 大量连接
小明同学很早就来到了教室,用过PLINQ之后,果然回不去了。从他的眼神中,我可以看得出来,小明对我今天的分享很是期待。
今天我们聊一个很酷炫,但是对于有多线程编程经验的人却不太好理解的编程模型:async 和 await。我们在介绍Task的那一篇文章中,就提到Task结合async和await可以非常优雅地写程序。今天我们就来一探究竟吧! 实例演示我们模拟一个需要异步执行的工作流,其中有3个主要步骤,且下一个步骤,需要依赖上一个步骤的结果: static Task<int> Func1(int seed){ return Task.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(){ var t1 = Func1(100); var w1 = t1.GetAwaiter(); w1.OnCompleted(() => { var t2 = Func2(w1.GetResult()); var w2 = t2.GetAwaiter(); w2.OnCompleted(() => { var t3 = 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(); var t2 = 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(){ var r1 = 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!"); await WorkFlow(); 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"); var r2 = 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#中多线程的那点事》终于告于段落了!感谢大家的阅读和支持,感谢小明同学的一路陪伴。分享干货知识,咱们下一个专题再见! 来源:外老师  小明同学
|