大批相连
上一则《C#中多处理器的司佥事-Linq & PLinq》,他们讲诉了一类崭新的子集并行计算的方式PLINQ。PLINQ借助扩充表达式,为他们提供更多了大批常见的子集时钟脉冲表达式。自此写标识符也变为了一件很融洽的事。
小华老师很早已走进了课室,所用PLINQ后,居然放不下了。从他的表情中,我能看得出,小华对我那时的撷取很是期盼。
那时他们聊两个很酷炫,但对有多处理器程式设计实战经验的人却不好认知的编程数学模型:async 和 await。他们在如是说Task的那一则该文中,就提及Task紧密结合async和await能十分典雅地写流程。那时他们就来一探到底吧!
示例演示
他们演示两个须要触发器继续执行的组织工作流,其中有3个主要就关键步骤,且下两个关键步骤,须要倚赖上两个关键步骤的结论:
static Task<int> Func1(int seed) { returnTask.Run(() => { Console.WriteLine($”seed: {seed}\t Func1 is Running… “); Task.Delay(2000).Wait(); return 1+ seed; }); }static Task<int> Func2(int p1) { return Task.Run(() => { Console.WriteLine($”p1:{p1}\t\t Func2 is Running… “); Task.Delay(2000).Wait(); return 2 + p1; }); } static Task<int> Func3(int p2) { return Task.Run(() => { Console.WriteLine($”p2:{p2}\t\t Func3 is Running… “); Task.Delay(2000).Wait(); return 3 + p2; }); }然后他们用常规Task来实现这个组织工作流的调所用程:
static void WorkFlow() { vart1 = Func1(100); var w1 = t1.GetAwaiter(); w1.OnCompleted(() => { var t2 = Func2(w1.GetResult()); varw2 = t2.GetAwaiter(); w2.OnCompleted(() => { vart3 = Func3(w2.GetResult()); t3.Wait(); Console.WriteLine($“Result:{t3.Result}”); }); }); } static void Main(string[] args) { Console.WriteLine(“Hello async/await World!”); WorkFlow(); Console.ReadKey(); }运行结论如下:
继续执行结论
从流程来看,没有太特殊的东西。但有一点,如果这个组织工作流的关键步骤增多,他们的WorkFlow这个表达式,不太典雅,当中的OnCompleted会越来越深。当然他们能使用Task的Wait表达式,来解决这个问题,但是这样的话WorkFlow这个子表达式内部,就会阻塞了:
static void WorkFlow() { var t1 = Func1(100); t1.Wait(); vart2 = Func2(t1.Result); t2.Wait();var t3 = Func3(t2.Result); t3.Wait(); Console.WriteLine($”Result:{t3.Result}“); }async/await
有没有保持WorkFlow不阻塞的方式呢?
有,就是借助async和await来改写:
static async Task WorkFlow() { varr1 =await Func1(100); var r2 = await Func2(r1); var r3 = await Func3(r2); Console.WriteLine($”Result:{r3}“); }static async Task Main(string[] args) { Console.WriteLine(“Hello async/await World!”); awaitWorkFlow(); Console.ReadKey(); }流程的结论,没有任何改变。但写法简化了十分多。几乎和他们写串行流程一样了。如果有老师不认知这里一定为什么一定要将WorkFlow写成非阻塞模式的,没关系,在将来某一天,你很可能会遇到这样的场景的。
await 就是 触发器等待的意思,await只能用于async修饰的表达式内部。async修饰的表达式,能像常规表达式一样调用。但在遇到async表达式内部的await关键字后,相当于该表达式就先返回了。待await等待的操作完成后,又会回到表达式中继续执行await后的标识符。是不是很绕。。。
async修饰的表达式,建议返回Task或者Task<T>,而不要返回void。
Main表达式,也能用async修饰,但建议返回Task。
流程继续执行流程也比较奇怪,很可能和你预想的继续执行顺序不同。为了研究流程继续执行的流程,我加入一些标记性的标识符,再来查看运行结论:
static async Task WorkFlow() { var r1 = await Func1(100); Console.WriteLine(“Func1 end”); varr2 =await Func2(r1); Console.WriteLine(“Func2 end”); var r3 = await Func3(r2); Console.WriteLine(“Func3 end”); Console.WriteLine($”Result:{r3}“); } static async Task Main(string[] args){ Console.WriteLine(“Hello async/await World!”); await WorkFlow(); Console.WriteLine(“WorkFlow end”); }结论如下:
继续执行流程
他们注意一点,在Main表达式中,加入await后,表达式继续执行的流程和串行继续执行十分像。
外老师第一次研究这个继续执行流程的时候,也是一脸懵逼。
感兴趣的老师,一定要动手单步继续执行这个流程,研究表达式继续执行的流程。
这个过程很难描述清楚,一定要亲自体验,才能发现其奥妙之处。
要是觉得这个流程奇怪的老师,能去了解一下目前很火的两个概念:协程。虽然我没的查到官方的资料表明async/await就是C#中的协程,但其用法和功能,都和协程十分像了。这其实是一类崭新的程式设计范式,在处理海量用户并发的时候,经常使用。了解协程后,认知这种流程就会容易很多。
async/await,或者说Task,是C#中的一类触发器程式设计数学模型。相比传统的Thread多处理器数学模型,更适合于高并发的场景。使用得当,能大幅提升流程的并行计算能力。
布置作业
通过调试以上的示例流程,了解async/await的继续执行流程。也能自行修改流程,演示不同的场景。
查询协程相关资料,辅助认知async/await
江湖再见
《C#中多处理器的司佥事》终于告于段落了!感谢大家的阅读和支持,感谢小华老师的一路陪伴。撷取干货知识,咱们下两个专题再见!
小明老师
系列该文
下面是给老师们准备的干货:
《C#中多处理器的那点事-死锁》