异步2 转

    在前文中,介绍了.NET下的多种异步的形式,在WEB程序中,天生就是多线程的,所以使用异步应该更为谨慎。本文将着重展开ASP.NET中的异步。编程

    【注意】本文中提到的异步指的是服务器端异步,而非客户端异步(Ajax)。缓存

    对于HTTP的请求响应模型,服务器没法主动通知或回调客户端,当客户端发起一个请求后,必须保持链接等待服务器的返回结果,才能继续处理,所以,对于客户端来讲,请求与响应是没法异步进行,也就是说不管服务器如何处理请求,对于客户端来讲没有任何差异。服务器

   

    那么ASP.NET异步指的又是什么,解决了什么问题呢?网络

    在解释ASP.NET异步前,先来考察下ASP.NET线程模型。session

 

 

    ASP.NET线程模型数据结构

 

 

    咱们知道,一个WEB服务能够同时服务器多个用户,咱们能够想象一下,WEB程序应该运行于多线程环境中,对于运行WEB程序的线程,咱们能够称之为WEB线程,那么,先来看看WEB线程长什么样子吧。多线程

    咱们能够用一个HttpHandler输出一些内容。并发

复制代码




















复制代码

    你能够看到相似于这样的结果:异步

    Name:async

    ManagedThreadId:57

    IsBackground:True

    IsThreadPoolThread:True

    这里能够看到,WEB线程是一个没有名称的线程池中的线程,若是刷新这个页面,还有机会看到 ManagedThreadId 在不断变化,而且可能重复出现。说明WEB程序有机会运行于线程池中的不一样线程。

    为了模拟多用户并发访问的状况,咱们须要对这个处理程序添加人为的延时,并输出线程相关信息与开始结束时间,再经过客户端程序同时发起多个请求,查看返回的内容,分析请求的处理状况。

复制代码



















复制代码

    咱们用一个命令行程序来发起请求,并显示结果。

复制代码

















复制代码

    这里,咱们同时发起了50个请求,而后观察响应的状况。

    【注意】后面的结果会由于操做系统、IIS版本、管道模式、.NET版本、配置项 的不一样而不一样,如下结果为在Windows Server 2008 R2 + IIS7.5 + .NET 4.5 beta(.NET 4 runtime) + 默认配置 中测试的结果,在没有特别说明的状况下,均为重启IIS后第一次运行的状况。
    这个程序在个人电脑运行结果是这样的: 

复制代码

















































复制代码

    从这个结果大概能够看出,开始两个请求几乎同时开始处理,由于线程池最小线程数为2(可配置),紧接着后面的请求会每隔半秒钟开始一个,由于若是池中的线程都忙,会等待半秒(.NET版本不一样而不一样),若是仍是没有线程释放则开启新的线程,直到达到最大线程数(可配置)。未能在线程池中处理的请求将被放入请求队列,当一个线程释放后,下一个请求紧接着开始在该线程处理。

    最终50个请求共产生24个线程,总用时约35.9秒。

    光看数据不够形象,用简单的代码把数据转换成图形吧,下面是100个请求的处理过程。

   

    咱们能够看到,当WEB线程长时间被占用时,请求会因为线程池而阻塞,同时产生大量的线程,最终响应时间变长。

    做为对比,咱们列出处理时间10毫秒的数据。

复制代码

















































复制代码

    共产生线程3个,总用时0.236秒。

    根据以上的数据,咱们能够得出结论,要提升系统响应时间与并发处理数,应尽量减小WEB线程的等待。

    【略】请各位自行查验当一次并发所有处理完毕后再次测试的处理状况。

    【略】请各位自行查验当处理程序中使用线程池处理等待任务的处理状况。

    如何减小WEB线程的等待呢,那就应该尽早的结果ProcessRequest方法,前一篇中讲到,对于一些须要等待完成的任务,可使用异步方法来作,因而咱们能够在ProcessRequest中调用异步方法,但问题是当ProcessRequest结束后,请求处理也即将结束,一但请求结束,将没有办法在这一次请求中返回结果给客户端,可是此时,异步任务尚未完成,当异步任务完成时,也许再也没有办法将结果传给客户端了。(难道用轮询?囧)

     咱们须要的方案是,处理请求时能够暂停处理(不是暂停线程),并保持客户端链接,在须要时,向客户端输出结果,并结束请求。

   

    在这个模型中,能够看到,对于WebServerRuntime来讲,咱们的请求处理程序就是一个异步方法,而对于客户端来讲,却并不知道后面的处理状况。不管在WebServerRuntime或是咱们的处理程序,都没有直接占用线程,一切由什么时候SetComplete决定。同时能够看到,这种模式须要WebServerRuntime的紧密配合,提供调用异步方法的接口。在ASP.NET中,这个接口就是IHttpAsyncHandler。

 

 

    异步ASP.NET处理程序

 

 

    首先,咱们来实现第一个异步处理程序,在适当的时候触发结束,在开始和结束时输出一些信息。 

复制代码
































































































复制代码

    在这里,咱们实现了一个简单的AsyncResult,因为ASP.NET经过回调方法获取异步完成,不会等待异步,因此不须要WaitHandle。在开始请求时,创建一个AsyncResult后直接返回,当异步完成时,调用AsyncResult的SetComplete方法,调用回调方法,再由ASP.NET调用异步结束。此时整个请求即完成。

    当咱们访问这个地址,能够获得相似于下面的结果:

    App:11240144 Begin:37:24,2676 ThreadId:6 End:37:29,2619 ThreadId:6

    能够看到开始和结束在同一个线程中运行。 

   

    当有多个并发请求时,线程池将忙碌起来,开始与结束处理也奖有机会运行于不一样的线程上。50个请求并发时的处理数据:

复制代码

















































复制代码

    能够看到,从始至终只由3个线程处理全部的请求,总共时间约5.12秒。

    为简化分析,咱们用下面的图来示意异步处理程序的并发处理过程。

    

    这样,咱们就能够经过异步的方式,将WEB线程撤底释放出来。由WEB线程进行请求的接收与结束处理,耗时的操做与等待都进行异步处理。这样少许的WEB线程就能够承受大量的并发请求,WEB线程将再也不成为系统的瓶颈。

    在大并发的异步模式下,和前面的数据相比较,能够看到HttpApplication的对象数量随并发处理数提升而提升,随之带来的一系列数据结构,如HttpHandler缓存,是须要考虑的内存开销。同时,在异步模式下,请求的完成须要编程的方式来控制,在触发完成前,客户端链接、HttpContext对象都保持活动状态,客户端也一直保持等待,直到超时。所以,异步模式下须要更细致的资源操做。

    咱们来看ASP.NET异步 的典型应用场景。

    场景一:处理过程当中有须要等待的任务,而且可使用异步完成的。

复制代码



















复制代码

    这个处理程序读取服务器的文件并输出到客户端。

复制代码


































复制代码

    这是一个简单的代理,服务器获取WEB资源后写回。

    在这类程序中,咱们提供的异步处理程序调用了IOCP异步方法,使得大量节省了WEB线程的占用,相比同步处理程序来讲,并发量会获得至关大的提高。

    【注意】前面提到,因为WEB线程属于线程池线程,所以,若是在线程池中加入任务,将一样会影响并发处理数。而在异步处理程序中,由线程池来完成异步将得不到任何本质上的提高,所以在异步处理程序中禁止操做线程池(ThreadPool.QueueUserWorkItem、delegate.BeginInvoke,Task.Run等)。若是肯定须要使用多线程来处理大量的计算,须要本身开启线程或实现本身的线程池。

复制代码







复制代码

    上面的代码将没法达到异步的效果。

    

    虽然等待工做交由另外一线程去操做,可是该线程与WEB线程性质相同,一样会致使其余请求阻塞。

    【思考】若是咱们的程序中的确须要有大量的计算,那么能够考虑将这些计算提取到独立的应用服务器中,而后经过网络IOCP异步调用,达到WEB服务器的高吞吐量与系统的平行扩展性。

    典型应用场景二:长链接消息推送。

    通常来讲,在WEB中获取服务器消息,采用轮询的方式,这种方式不可避免会有延时,当咱们须要即时消息的推送时(如WEBIM),须要用到长链接。

    长链接方式,由客户端发起请求,服务器端接收后暂停处理并保持链接,当须要发送消息给客户端时,输出内容并结束处理,客户端获得消息或者超时后,再次发起链接。如此达到在HTTP协议上服务器消息即时推送到客户端的目的。

  

    在这种状况下,咱们但愿服务器尽量长时间保持链接,若是采用同步处理程序,则链接数受到服务器线程数的限制,而异步处理程序则能够很好的解决这个问题。异步处理程序开始时,收集相关信息,并放入集合后返回异步结果。当须要向这个客户端发送消息时,从客户端集合中找到须要发送的目标,发送完成便可。

    首先,咱们须要对客户端进行标识,这个标识每每采用sessionid来作,本例中简单起见,经过客户端传递参数获取。

复制代码






















































复制代码

    咱们须要一个集合来保存链接中的客户端,提供一个向这些客户端发送消息的方法。

复制代码


























复制代码

    对于异步处理程序的开始方法,咱们收集信息并放入集合。

复制代码







复制代码

    【不完善】因为客户端收到一次消息后结束请求,由客户端再次发起请求,中间会有部分时间间隙,在这间隙中向该客户端发送的消息将丢失,解决方案是维护另外一个用户是否在线的表,若是用户不在线,则处理离线消息,若是在线,而且正在链接中,则按上述处理,若是不在链接中,则缓存在服务器,当客户端再次链接时,首先检查缓存的消息,若是有未接消息,则获取消息并当即返回。

    发送消息的处理程序。

复制代码

















复制代码

    能够在任何须要的位置向客户端发送消息。

    【不完善】咱们须要定时刷新客户端集合,对于长时间未处理的客户端进行超时结束处理。

    经过异步处理程序构建的长链接消息推送机制,单台服务器能够轻松支持上万个并发链接。

 

 

    异步Action

 

 

    在ASP.NET MVC 4中,添加了对异步Action的支持。     

    

    在ASP.NET MVC4中,整个处理过程都是异步的。

    在图中能够看到,最右边的ActionDescriptor将决定如何调用咱们的Action方法,而如何调用是由具体的Action方法形式决定,ASP.NET MVC会根据不一样的方法形式建立不一样的ActionDescriptor实例,从而调用不一样的处理过程。对于传统的方法,则使用ReflectedActionDescriptor,他实现Execute方法,调用咱们的Action,并在AsyncControllerActionInvoker包装成同步调用。而异步调用在ASP.NET MVC 4  中有两种模式。

 

    异步Action模式一:AsyncController/XXXAsync/XXXCompleted

 

    咱们可使一个Controller继承自AsyncController,按照约定同时提供两个方法,分别命名为XXXAsync/XXXCompleted,ASP.NET MVC则会将他们包装成ReflectedAsyncActionDescriptor。   

复制代码



















复制代码

     因为没有IAsyncResult,咱们须要经过AsyncManager来告诉ASP.NET MVC什么时候完成异步,咱们能够在方法内部在启用异步时调用AsyncManager.OutstandingOperations.Increment()告诉ASP.NET MVC开始了一次异步,完成异步时调用AsyncManager.OutstandingOperations.Decrement()告诉ASP.NET MVC完成了一次异步,当全部异步完成,AsyncManager会自动触发异步完成事件,调用回调方法,最终调用咱们的XXXComplete方法。咱们也能够用AsyncManager.Finish()也触发全部异步完成。当不使用任何AsyncManager时,则不启用异步。

  

    能够看到整个异步过程由ASP.NET完成,在适当的时候会调用咱们的方法。异步的开始、结束动做与及如何触发完成都在咱们的代码中体现。
 

    异步Action模式二:Task Action

 

    对于Action,若是返回的类型是 Task,ASP.NET MVC则会将他们包装成TaskAsyncActionDescriptor。 

复制代码











复制代码

     我只须要需提供一个返回类型为Task的方法便可,我里咱们采用async/await语法构建一个异步方法,在方法内部调用其余的异步方法。

   

    相比以前的模式,简单了一些,特别是咱们的Controller中,只有一个方法,异步的操做都交由Task完成。对于能够返回Task的方法来讲(如经过async/await包装多个异步方法),就显得十分方

相关文章
相关标签/搜索