![f645bfaa454eeded598e545e9aab8cd7.png](https://i-blog.csdnimg.cn/blog_migrate/3f61fa6db7a44f32e455cbd0d357be88.jpeg)
大量连接
上一篇《C#中多线程的那点事-Linq & PLinq》,我们讲述了一种全新的集合并行处理的方法PLINQ。PLINQ利用扩展函数,为我们提供了大量常用的集合并行操作函数。从此写代码也变成了一件很愉快的事情。
小明同学很早就来到了教室,用过PLINQ之后,果然回不去了。从他的眼神中,我可以看得出来,小明对我今天的分享很是期待。
今天我们聊一个很酷炫,但是对于有多线程编程经验的人却不太好理解的编程模型:async 和 await。我们在介绍Task的那一篇文章中,就提到Task结合async和await可以非常优雅地写程序。今天我们就来一探究竟吧!
实例演示
我们模拟一个需要异步执行的工作流,其中有3个主要步骤,且下一个步骤,需要依赖上一个步骤的结果:
static Task Func1(int seed){ return Task.Run(() => { Console.WriteLine($"seed: {seed} Func1 is Running... "); Task.Delay(2000).Wait(); return 1 + seed; });}static Task Func2(int p1){ return Task.Run(() => { Console.WriteLine($"p1:{p1} Func2 is Running... "); Task.Delay(2000).Wait(); return 2 + p1; });}static Task Func3(int p2){ return Task.Run(() => { Console.WriteLine($"p2:{p2} 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();}
运行结果如下:
![8f51661ab544106e4251c3a62ea297c6.png](https://i-blog.csdnimg.cn/blog_migrate/2235545e54165c3abd5b2f165d482ad5.jpeg)
执行结果
从程序来看,没有太特殊的东西。但是有一点,如果这个工作流的步骤增多,我们的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 void WorkFlow(){ var r1 = await Func1(100); var r2 = await Func2(r1); var r3 = await Func3(r2); Console.WriteLine($"Result:{r3}");}static void Main(string[] args){ Console.WriteLine("Hello async/await World!"); WorkFlow(); Console.ReadKey();}
程序的结果,没有任何改变。但是写法简化了非常多。几乎和我们写串行程序一样了。如果有同学不理解这里一定为什么一定要将WorkFlow写成非阻塞模式的,没关系,在将来某一天,你很可能会遇到这样的场景的。
await 就是 异步等待的意思,await只能用于async修饰的函数内部。async修饰的函数,可以像常规函数一样调用。但是在遇到async函数内部的await关键字之后,相当于该函数就先返回了。待await等待的操作完成之后,又会回到函数中执行await之后的代码。是不是很绕。。。
此处应该有流程图
程序执行流程也比较奇怪,很可能和你预想的执行顺序不同。为了研究程序执行的流程,我加入一些标记性的代码,再来查看运行结果:
static async void 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 void Main(string[] args){ Console.WriteLine("Hello async/await World!"); WorkFlow(); Console.WriteLine("WorkFlow end"); Console.ReadKey();}
结果如下:
![5feb591a9b064bd80a4143c1b059164b.png](https://i-blog.csdnimg.cn/blog_migrate/9bc9288bee0c77affe34a37224981bf7.jpeg)
执行流程
我们注意一点,WorkFlow这个函数,在Fun1执行结束之前(第一个await后),就已经返回了,其后的代码就开始执行了。这也是为什么要在最后面加上【Console.ReadKey();】的原因,这是为了阻止程序即出。
外老师第一次研究这个执行流程的时候,也是一脸懵逼。
感兴趣的同学,一定要动手单步执行这个程序,研究函数执行的流程。
这个过程很难描述清楚,一定要亲自体验,才能发现其奥妙之处。
要是觉得这个流程奇怪的同学,可以去了解一下目前很火的一个概念:协程。虽然我没的查到官方的资料表明async/await就是C#中的协程,但是其用法和功能,都和协程非常像了。这其实是一种全新的编程范式,在处理海量用户并发的时候,经常使用。了解协程之后,理解这种流程就会容易很多。
布置作业
通过调试以上的示例程序,了解async/await的执行流程。也可以自行修改程序,模拟不同的场景。
查询协程相关资料,辅助理解async/await
江湖再见
《C#中多线程的那点事》终于告于段落了!感谢大家的阅读和支持,感谢小明同学的一路陪伴。分享干货知识,咱们下一个专题再见!
![0c51ae165db1f8a503dec706a0398795.gif](https://i-blog.csdnimg.cn/blog_migrate/13e42fdfef8c493a305ffc33d4594d0f.gif)
小明同学
系列文章
下面是给同学们准备的干货:
《C#中多线程的那点事儿-Thread入门》
《C#中多线程的那点事-多线程的代价》
《C#中多线程的那点事-线程池》
《C#中多线程的那点事-锁》
《C#中多线程的那点事-死锁》
《C#中多线程的那点事-Task再次起航》
《C#中多线程的那点事-Parallel》
《C#中多线程的那点事-Linq & PLinq》