乐趣区

关于c++:异步方法和TPL-async-await-Parallel

封装

咱们要把下面这个 Task 封装成办法,怎么办?

最重要的一点,这个办法要能返回生成的 random,前面的代码要用!

public static Task<int> getRandom() { return Task<int>.Run(() => {Thread.Sleep(500); // 模仿耗时 return new Random().Next(); }); }

@想一想 @:应该如何调用这个办法?(_提醒:不要间接__getRandom().Result_)

如果咱们还须要进一步的封装,增加一个办法 Process,外面调用 getRandom()并把其后果输入:

public static void Process() { Task<int> task = getRandom(); Console.WriteLine(task.Result); }

故技重施,如同不行了,这次……

@想一想 @:再让 Process()返回 Task 行不行?一个 Task 套另一 Task 会呈现什么状况?

在 getRandom()和 Process()中展现线程 Id 看一看:

Console.WriteLine(“in getRandom() with Thread-” + Thread.CurrentThread.ManagedThreadId);

在.NET core 的 I / O 类库中,咱们会发现这样的办法:

public static Task AppendAllLinesAsync(string path, IEnumerable<string> contents, Encoding encoding, CancellationToken cancellationToken = default); public static Task<byte[]> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default);

留神:

  • 办法名被增加了 Async 后缀(举荐命名标准)
  • 办法的返回类型为 Task 或 Task<T>

异步办法

.NET 为咱们提供了简洁优雅的异步办法,只须要两个关键字:

async 和 await

被 async 标记的办法被称为异步办法,

  • 然而,async 不肯定(没有强制力保障)异步。同步的办法一样能够标记 async。async 的作用只是:
  • 通知编译器办法中能够呈现 await。如果只有 async 没有 await,报编译时正告

只有 await 没有 async,报编译谬误。

static async void Process() { int random = await getRandom(); Console.WriteLine(random); }

await,能够了解为:异步(async)期待,后接 awaitable 实例。

咱们能够简略的把 awaitable 了解成 Task。

非阻塞期待

异步办法始终同步运行,直到 await。

从 await 开始异步(分叉):

  • 执行 awatable 中的内容,同时
  • 返回办法的调用处,执行其后内容

直到 awaitable 中内容执行结束,才暂停办法调用处内容,继续执行 await 之后的代码。

异步办法执行结束,持续办法调用处内容。

以上述代码为例:

//33 –> 44 –> 45 –> 46 –> 47 –> 调用异步办法处 // +–> 52 –> 57– + 被调用异步办法 // +–> 35 –> 38 +–> 39 awaitable

留神:如果 52 行之前还有一般(非异步)代码,这些代码不会被异步执行。

await 不像 Wait()或 Result 一样,

开始(但不是立刻或同步的)
async 和 await 会不会开启一个新的工作(或者线程)?不会。

异步办法分为两种:

返回 void 或 Task

public static async void Getup() { Console.WriteLine($”before await-1 with thread {Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”before await-2 with thread {Thread.CurrentThread.ManagedThreadId}”); //await 之前的代码,在主线程上运行 // await Task.Run(()=> { Console.WriteLine($”in await with thread {Thread.CurrentThread.ManagedThreadId}”); }); // Console.WriteLine($”after await-3 with thread {Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”after await-4 with thread {Thread.CurrentThread.ManagedThreadId}”); }

从 await 开始,代码开始分叉(只是异步,不肯定新开线程):

  • 一边执行 await 后的表达式(Task)
  • 一边返回到办法调用者处继续执行

直到 await 后的 Task 执行结束,才会返回 async 办法,继续执行其 await(非阻塞)之后的残余代码。

Console.WriteLine($”before async-1 with thread {Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”before async-2 with thread {Thread.CurrentThread.ManagedThreadId}”); Getup(); for (int i = 0; i < 10; i++) {//Getup()里 await 局部的运行,会打乱这里代码的同步运行 Console.WriteLine($”after async-{3 + i} with thread {Thread.CurrentThread.ManagedThreadId}”); }

本质上,await 采纳的是 Task 的 ContinueWith()机制:await 之后的办法内代码,被 await Task 执行结束后调用。

比照演示:

  • 非异步办法:只有 Task 异步执行
  • 调用 Wait()的非异步办法:Wait()会阻塞以后线程进行期待

异步办法中的 void 能够被间接替换成 Task(举荐),以便于该办法进一步的被 await 传递。

void 通常做为顶级(top-level)办法应用。

思考:当 async 办法中抛出异样,void 办法和 Task 办法的区别?

返回 Task<T>

返回值被 Task 包裹,写成 Task<T>,T 指办法体内申明返回的类型

// 办法的申明:返回的是 Task<int> public static async Task<int> Getup() { int result = await Task<int>.Run(() => {Thread.Sleep(500); Console.WriteLine($”at await in Getup() with thread {Thread.CurrentThread.ManagedThreadId}”); return new Random().Next(); }); // 办法体内,返回的是 int return result; }

特地留神:不能间接 Getup().Result 或 await Getup()取值,否则……

思考:和间接返回 Task<T> 的区别?

工作并行库Task Parallel Library

.NET 中 System.Threading 和 System.Threading.Tasks 名称空间下的类库

简化异步 / 并行开发,在底层实现:

  • 动静调整并行规模
  • 解决分区
  • 线程(池)调度(器)等……

于 Task 的并行

最简略的例子,Parallel.Invoke():

for (int i = 0; i < 5; i++) {Console.WriteLine(); Parallel.Invoke(() => {Console.WriteLine(i + $”:task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} begin in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} end in thread-{Thread.CurrentThread.ManagedThreadId}”); }, () => { Console.WriteLine(i + $”:task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} in begin in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}”); Console.WriteLine($”task-{Task.CurrentId} in end in thread-{Thread.CurrentThread.ManagedThreadId}”); } ); }

其余办法:

  • For 循环

    Parallel.For(0, 10, x => { Console.WriteLine(x); });

  • ForEach

    Parallel.ForEach(Enumerable.Range(1,10), x => Console.WriteLine(x));

引入线程数组:Task[]

  • WaitAll / WaitAny:
  • WhenAll / WhenAny:

比照以下代码,领会 await 的 continuation:

public static async Task Getup() { //await Task.Run(() => {Console.WriteLine(“ 洗脸 ”); }); //await Task.Run(() => { Console.WriteLine(“ 刷牙 ”); }); //await Task.Run(() => { Console.WriteLine(“ 吃早餐 ”); }); //await Task.Run(() => { Console.WriteLine(“ 背单词 ”); }); Task[] tasks = { Task.Run(() => {Console.WriteLine(“ 洗脸 ”); }), Task.Run(() => { Console.WriteLine(“ 刷牙 ”); }), Task.Run(() => { Console.WriteLine(“ 吃早餐 ”); }), Task.Run(() => { Console.WriteLine(“ 背单词 ”); }) }; await Task.WhenAll(tasks); }

补充:

Delay()

FromResult()

AsyncState

并行 Linq Parallel LINQ (PLINQ)

仅实用于 Linq to Object,次要的措施是:对数据源进行分区,而后多核并发运行(激进模式:如果能不并发就不并发)

外围办法:AsParallel(),在数据源后增加。

try {IEnumerable<int> numbers = Enumerable.Range(0, 1000); var filtered = numbers.AsParallel() //.Where(n => n % 11 == 0) .Where(n => 8 % (n > 100 ? n : 0) == 0) ; filtered.ForAll(f => Console.WriteLine(f)); } catch (AggregateException ae) {ae.Handle(e => { Console.WriteLine(e); return true; }); }

ForAll():同样能够并发执行

依然是 AggregateException 异样

最佳实际

应用异步 / 并行的副作用(side effect):

  1. 减少代码的复杂性(尤其是 bug 调试)
  2. 异步 / 并行的切换须要耗费额定的资源

简略了解:

  • 锁、死锁(Deadlock)、资源抢夺(race condition)
  • 线程平安(Thread Safty)
  • 天下没有收费的午餐
  • 越是简单精美的货色越不“耐操”(健壮性 robust)

总是最初思考异步 / 并行:(集体倡议)

  • 总是在最初思考异步 / 并发(尤其是 B / S 架构)
  • 确定性能瓶颈
  • 确定该瓶颈能够通过异步 / 并行的办法解决
退出移动版