自从有了.NET 4.5,我们又多了一个装逼语法:async,await。但如果错用就会装逼不成反变傻逼。首先我们得明白在ASP.NET中async await所针对的问题,这样才能正确的装逼。于是我们就不得不先研究一下线程池。
在IIS服务器上,处理Http请求的是线程,和Windows的其他软件一样,干活的永远是线程,而不应该说是进程。一个线程同时只能处理一个request,而web上的request不可能同时永远只有一个,所以线程需要和他的小伙伴们一起组成线程池,才能保证网站的响应。当一个线程处理完了手头的请求,它就被释放掉了,于是如果再有新的请求进来他就能再去处理。但如果当线程用完了,并且他们正在处理的请求都没完成,网站就卡住了,用户就只能等出翔。这时候IIS就会返回一个HTTP 503爆给用户。
打个比方,IIS服务器就好像银行,Http请求就好像顾客,银行开的窗口数量就是进程池里的进程数量。比如银行同时开了5个窗口,只来了一个顾客,那其他4个窗口就是空闲状态,如果再来顾客,就会被分配到其他窗口。如果来了20个顾客,那5个窗口都会满,在任意一名顾客完成他的业务离开柜台之前,后面的顾客就只能排队等出翔了。
说了这么多,还是没有说线程池和async await的关系。简单的说,如果不用async,那么request的处理是同步的,处理它的线程在这个request的工作完成之前一直处于忙状态,此时他是不能处理别的request的。这就好像银行的一个窗口前来了一名顾客,结果那个顾客业务办到一半,突然对柜台说”我要去拉翔“,如果你不用async,那么这个窗口的营业员就必须等到这位顾客把翔拉完回来继续办业务,在顾客拉翔期间,虽然他不再占用这个窗口的资源,但这个窗口还是傻乎乎的处于忙状态,就是不给下一个顾客办理业务。而如果你用了async await,那么在这位顾客出去拉翔的时候,这个窗口的营业员就可以给下一个顾客办理业务,但是,要注意,当刚才那位顾客把翔拉完回来的时候,继续处理他业务的不一定是刚才那个窗口了!!!这就是为什么在async await里,await回来的请求未必能找到刚才的httpcontext。
通过刚才的比喻,不难发现,拉翔这件事和窗口的资源无关,这就好像IO操作读写文件和从远程服务器取数据一样,这种操作才是适合await的。如果是要服务器CPU参与的工作,则不用await。
当然,await的位置是有讲究的,你的代码能不能让一位顾客在拉翔的时候去干别的事?(注意下面的代码所想要表达的意思已经不仅仅是某一个线程了)
如果你是这样写的:
async Task Foo() { var xiang = await customer.PullShitAsync(); DoSomething(); }
那么DoSomething()依然会等到顾客把翔拉完才执行。
如果你想在顾客拉翔的时候DoSomething(),就应该这么写:
async Task Foo() { var xiangTask = customer.PullShitAsync(); DoSomething(); var xiang = await xiangTask; }
如果你有一堆顾客要拉翔,不应该这样写:
foreach(var c in customers) { await c.PullShitAsync(); }
这样会导致一个顾客在拉翔的时候,下一个顾客必须等他拉完才能拉。
而要这样写:
var shitTasks = new List<Task>(); foreach(var c in customers) { shitTasks.Add(c.PullShitAsync()); } await Task.WhenAll(shitTasks);
这样就允许大家一起拉翔,后去拉翔的人可能比前去拉翔的人先拉完。
当然,顾客拉翔也可能出现意外,比如:
一坨翔拉了一天:RequstTimeout
拉着拉着人没了:ClientDisconnected
这些都是ASP.NET自带的Cancellation token。意思就是当这些情况发生的时候,不要傻乎乎的await了。