.NET中的异步编程(一)-为什么需要异步

  在2010年的PDC上,微软发布了Visual Studio Async CTP,大大地降低了异步编程的难度,让我们可以像写同步的方法那样去编写异步代码。Async CTP也在社区里掀起了不小的波澜。在这之后,我也学习了一段时间,这个系列会将这段时间的学习作个梳理。

  好了,下面进入本文的正题。

  为什么需要异步编程

  既然同步的写法更自然简单,异步的代码(传统的)不好写,还容易出错,那我们为什么需要去编写异步的代码呢?微软还要费这么大劲投入对Async CTP的开发呢?这其中肯定有一些原因。

  快速响应的用户界面

  作为电脑的资深用户,我们肯定有多次“漏斗式鼠标”,“转圈式鼠标”的体验吧。点击一个按钮,然后鼠标就在那儿不停的转圈,再在界面上点两下,界面变灰,标题栏上出现“没有响应”。然后我们束手无策,性子好点的就在那儿等待一会儿,看看能不能恢复过来;性子不好的就打开任务管理器杀掉进程,杀掉进程容易,但有可能会破坏重要数据。

  那造成这种情况到底是什么原因呢?概括成一句话就是:耗时的操作阻塞了UI线程,造成UI线程不能响应用户操作。关于更底层的原因请移步我的这篇文章:WinForm二三事(一)消息循环。那么这个时候我们就需要一种机制,在发起耗时操作的请求之后要立即返回,不要阻塞UI线程,让UI线程可以继续响应用用户的操作。然后等耗时操作返回后,通过回调来处理耗时操作返回的结果。下面是在UI上使用同步的方式和异步的方式的示意图:

image

image

  更高的伸缩性

  对于服务器应用来说,一般都是一个线程处理一个请求。另外一点是,线程的创建和销毁是昂贵的(这一点可以参考《CLR via C#》中Thread Baisc一章的描述),而服务器的资源肯定是有限的;并且,线程创建的越多,线程上下文切换就会变得越频繁。所以,为了创建高可伸缩性的服务,我们必须用最少的线程处理更多的请求,这样不仅能够做到消耗更少的资源(创建更少的线程),而且在应对请求突发增长的情况也很有用处,那么这里非常重要的一点就是不要阻塞线程,让线程池能够高效的工作。而且,在服务端应用中,有非常多的IO操作:数据库访问,磁盘操作,Socket访问等。对于这些IO操作,不属于计算密集型操作,是不需要单独分配一个线程来处理的。

  要做到高可伸缩性,异步是一剂良药。假设现在这是一个web应用,当用户的HTTP request到来时,线程池提供一个线程来处理(忽略前面的排队等过程),然后到某一点,我们肯定需要读取磁盘、访问数据库,这个时候我们使用异步的方式,发起IO请求,然后处理HTTP request的线程就可以返回到线程池了,它可以继续处理其他请求,不需要在这里等待IO操作的返回。当IO操作完成之后,会通过回调(具体实现方式请参照后续文章)完成刚才那个HTTP reqeust后续的处理。

  下面是使用同步方式和异步方式的示意图:

image

  上图只画出了一个请求,高亮显示的那一段其实是不需要占用线程的,其实这段时间该线程可以返回线程池,然后分配去做其他请求,而数据库返回结果之后,再从线程池里分配一个线程来处理后续操作。这样,如果请求多的话,线程池就会创建更多的线程来处理请求,最后结果大家应该都知道了。

image

  从上图可以看出,开始的时候来自线程池的thread1处理请求,然后发起对数据库的请求,发起操作完毕后,thread1被线程池回收;当数据库将结果返回时线程池选择另外一个线程thread2(有可能是原来的那个线程,如果空闲的话)来处理数据库返回的结果,完成后续的操作。对于IO操作非常多的服务来说,所获得的益处是不可估量的。

  后记

  本文主要从创建响应灵敏的用户界面和创建高可伸缩性的服务应用这两种不同的应用场景来阐释我们为什么需要异步。至于如何进行异步开发在后续的文章我会首先介绍传统的异步和Async CTP以及F#中的Async Workflow。

NET技术.NET中的异步编程(一)-为什么需要异步,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。