概述
先吐个槽,.NET的TPL框架,以及这篇文章想要表述的async await关键字,都是.NET语言层面本身支持的一种异步框架,代表其在编译时是可以最大化的被优化,作为内部DSL来说,.NET一直是非常优秀的一个平台,有的语言只是在设计模式层面提供了内部DSL的框架,在优化上肯定是不及这种语言层面支持的机制。期待它在未来的升级,例如和Go语言中的Goroutine一样,包装了线程、协程的机制自动调度。
目前有很多流行的开源框架,在架构的运行视图中,都是采用了线程池做异步的实现,是粒度的变更,例如原来ASP.NET是单线程处理一个请求,发展到了现在可以由开发人员通过async await自己来定义线程池处理的粒度,通过细粒度或者说是开发人员自定义粒度的线程池中线程切换做到自定义的并行计算与吞吐量的上升。
测试async await的场景
代码如下
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Master(); 7 8 Console.ReadKey(); 9 } 10 11 12 private static void Master() 13 { 14 Task task1 = SlaveWebContent1("http://www.163.com"); 15 16 Task task2 = SlaveWebContent2("http://www.sina.com"); 17 18 Task task3 = SlaveWebContent3("http://www.tencent.com"); 19 20 Task.WaitAll(task1, task2, task3); 21 } 22 23 private static async Task<string> SlaveWebContent1(string url) 24 { 25 WebClient webClient = new WebClient(); 26 string webContent=await webClient.DownloadStringTaskAsync(url); 27 28 return webContent; 29 } 30 31 private static async Task<string> SlaveWebContent2(string url) 32 { 33 WebClient webClient = new WebClient(); 34 string webContent = await webClient.DownloadStringTaskAsync(url); 35 36 return webContent; 37 } 38 39 private static async Task<string> SlaveWebContent3(string url) 40 { 41 WebClient webClient = new WebClient(); 42 string webContent = await webClient.DownloadStringTaskAsync(url); 43 44 return webContent; 45 }
在并行计算中,我们并不总是能恰到好处的全部采用并行的方式,所以粒度是需要可以自我定义的,async await这对关键字以方法作为载体实现了粒度的掌控。
在代码中SlaveWebContent方法应该是1个,不过为了我们来看看线程的运行模式,所以写了3个。
让我们来运行吧。
我们可以理解为主线程ID为17248。
进入了第一个方法,仍然是17248,在下面的2,3方法中,只要是在关键字await之前的代码都会是17248这个ID的线程在运行。
来看看await执行后的代码。
这里是方法3首先返回结果,因为是异步的,我们无法知道多久会返回,线程ID为13892。
方法2返回了结果,线程ID为14776。
最后是方法1返回结果,线程ID也会不一样。让我们直接跳到Master方法中WaitAll方法。
回到了主线程。
这样就执行完了整个程序。对我们来说,在每一个Slave方法内,await关键字就像一个分隔符一样,之前的代码是调用该方法的线程,await之后是将结果带回来的线程,await执行的方法是异步的,在.NET平台下,例如我们程序中的例子,实际上是调用了线程中的IO线程去下载网页内容,IO线程会怎么做呢,我没有往下看,不过我们也可以猜测一下,Reactor或者Proactor,Widnows提供了IOCP来作为非阻塞套接字的实现方式,也许是使用了这种方式。也许是这样吧,不过我们不用关心,我们只专注于await这里即可。
总结
如果结合目前很火的事件驱动,那么通过线程来实现的并发或者并行,对吞吐量是很大的提升。ASP.NET中目前也是采取的这种方式,接收到请求后,将请求投递至Worker线程池,Worker线程池将请求投递至用户处理线程池,大概是这样,用词不准确,粒度上划分就是2个大的职责,进入我们的代码中之后,我们是可以通过async await关键字将粒度再细分,这才是获得吞吐量的关键。当await处理完毕后,将处理带回来的线程将结果投递至调用的线程后,回归线程池去处理其他的事情,也许是新请求,也许是其他方法。
粒度的可操作性很大,所以也会带来一些问题,究竟我们什么时候使用它,对于短时的操作,同步性能会更好并且语义上更直观;对于要用到IO的操作,使用async await肯定是最好的场景;对于超长时的操作,自定义线程单独跑肯定是最好的,不会妨碍线程池的线程处理其他请求,线程池中的线程应该是处理时长合适的场景,时长合适就是我们的粒度选择是否合适的问题,需要在实际项目中进行测试,所以在合适的入口选择使用async aswait,在后续操作中审时使用同步。
也许在未来,会不会也会出现类似于CPU的投机运行机制呢,呵呵,很期待。