最近研究异步请求,稍微总结一下,以后继续补充:
ASP.NET 一直支持同步和异步 HTTP 处置程序。当初,ASP.NET 2.0 拥有了一些新增功能,可以使开发人员更加方便快捷地创建异步页面。尤其是对于基于服务器的应用程序,异步操纵是实现可伸缩性的基本。如果须要扩展现有 Web 应用程序,首先要斟酌的是能向页面添加多少异步功能。
在这方面,ASP.NET 的行为与代表多个客户端执行某些后台工作的任何其他服务器应用程序十分相似。每个传入请求都分配给 ASP.NET 拥有的线程,该线程是从 ASP.NET 线程池选择的。在操纵终止并为客户端生成某种响应之前,该线程将一直被阻止。线程要等待多长时光?ASP.NET 运行时环境可配置为定义一个自定义超时(默许值为 90 秒),但防止线程被阻止更为重要。
在处置可能耗时很长的操纵时,超时最多只能确保该线程在给定秒数以后得到释放并返回到池中。现实上,您所须要的是防止长时光阻止该线程。理想情况下,您须要该线程开始一个请求,然后将其交给某个其他非 ASP.NET 线程。在将响应发送到客户端的操纵完成时,将再次选择统一个线程或 ASP.NET 池中的另外一个线程。这种模式称为异步 ASP.NET 页面。
就异步操纵而言,应辨别相对于用户的异步页面和相对于 ASP.NET 运行时的异步页面。对于相对于用户的异步页面,唯一的可行方法是 AJAX 操纵。但是,使用 AJAX 来执行速度可能较慢的操纵会降低对最终用户的影响,但不会为 ASP.NET 运行时带来任何缓解。
异步页面和 ASP.NET 运行时
线程在请求上挂起的时光越长,为了处置新的传入请求而从 ASP.NET 池减去一个线程的时光就越长。当没有可用于处置新请求的线程时,就会将这些请求排队。这可能会致使延迟和总体性能下降。
在 ASP.NET 中,HTTP 处置程序在默许情况下是同步的。必须通过应用略微不同的接口显式构建和实现异步 HTTP 处置程序。同步处置程序与异步处置程序的重要不同之处在于,异步处置程序使用下列方法(IHttpAsyncHandler 接口的组成部份),而不是使用同步 ProcessRequest 方法:
IAsyncResult BeginProcessRequest( HttpContext context, AsyncCallback cb, object extraData); void EndProcessRequest( IAsyncResult result);
BeginProcessRequest 包括为处置请求而须要执行的操纵。此代码应计划为在辅助线程上启动操纵并立即返回。EndProcessRequest 包括用于完成先前已启动的请求的代码。
可以看到,一个异步 HTTP 请求分成两个部份,即“异步点”之前和以后,异步点是请求生命周期中拥有请求的线程产生更改的点。当达到异步点时,原始 ASP.NET 线程向另外一个线程交出控制权。这个可能耗时较长的操纵产生在 ASP.NET 请求的两个部份之间。异步请求的每个部份都独立于另外一部份运行,就线程而言没有任何相关性。换句话说,不能保证用统一线程处置请求的两个部份。现实效果是,在操纵过程中不会阻止任何线程。
此时,明显的问题是:哪个线程现实担任处置“耗时较长”的操纵?ASP.NET 在内部使用 I/O 完成端口来跟踪请求的终止。当达到异步点时,ASP.NET 将挂起的请求绑定到一个 I/O 完成端口,并注册一个回调以在终止请求时获取通知。操纵系统将使用自己的专用线程之一来监视操纵的终止,从而使 ASP.NET 线程不必在完整闲暇的状态下进行等待。操纵终止时,操纵系统将一条消息放置在完成队列中,该消息将触发 ASP.NET 回调,随后,该回调获取自己的线程之一来恢复请求。如前所述,I/O 完成端口是操纵系统的一个功能。
异步页面的现实特性
在 ASP.NET 中,如果斟酌改进给定页面(担任执行可能耗时较长的操纵)的性能,通常就涉及异步页面。不过,还应注意几个其他问题。从用户的角度来看,同步和异步请求看起来几乎雷同。比如,如果请求的操纵预期须要 30 秒完成,则用户将至少等待 30 秒才能获得新的页面。无论是同步还是异步实现该页面,都市涌现这种情况。此外,如果某个异步页面耗费略微长一点时光才完成一个请求,也很正常。那么,异步页面有何长处?
可伸缩性和性能其实不完整一样。或者至少,可伸缩性与性能有关,不过是在不同级别上,即整个应用程序而不是单个请求。异步页面的长处是,可以显著增加 ASP.NET 池中的线程的工作量。这其实不会使耗时较长的请求运行更快,但它可帮助系统按通常方式处置耗时不长的请求,也就是说,不会因运行较慢的请求而产生任何特别延迟。
异步请求利用异步 HTTP 处置程序,这一直是 ASP.NET 平台的一个功能。但是,ASP.NET Web 窗体和 ASP.NET MVC 都供给了自己的功能,以便开发人员更方便地实现异步操纵。在本文其余部份,我将讨论 ASP.NET MVC 2 中的异步操纵。
异步控制器操纵
在 ASP.NET MVC 1.0 中,任何控制器操纵都只能以同步方式运行。不过,MVC Futures 库中添加了一个新的 AsyncController 类。在一段试验期以后,控制器的异步 API 已正式添加到 ASP.NET MVC 框架中,从 ASP.NET MVC 框架的版本 2 开始,该异步 API 已完整可用并以文档情势进行了说明。(本文讨论的是 ASP.NET MVC 2 RC 的语法和功能。)如果使用 MVC Futures 库中的 AsyncController 类,您会注意到有一些变更,API 变得更加简单清晰了。
AsyncController 的目标是确保任何公然的操纵方法都以异步方式执行,而不转变 ASP.NET MVC 框架所特有的总体编程方法。图 1 中的图表现了异步操纵处置以后的步骤顺序。
图 1 ASP.NET MVC 中异步操纵方法的机制
异步点位于正在执行的事件和已执行的事件之间。当操纵调用程序通知将要执行操纵时,所使用的线程仍然是从 Web 服务器队列选择请求的原始 ASP.NET 线程。此时会执行该操纵。最后,当操纵调用程序准备好通知已执行完操纵的事件时,可能另外一个 ASP.NET 线程会担任处置该请求。图 2 说明了这种方案。
图 2 异步操纵方法调用的线程切换
在详细讨论如何创建和调试异步方法之前,应说明异步 ASP.NET 操纵的另外一个基本问题:不是全部操纵都合适以异步方式执行。
异步操纵的现实目标
只有受 I/O 制约的操纵才合适成为异步控制器类上的异步操纵方法。受 I/O 制约的操纵是不依赖于本机 CPU 完成的操纵。当受 I/O 制约的操纵处于活动状态时,CPU 只是等待外部存储(数据库或近程服务)对数据进行处置(即下载)。受 I/O 制约的操纵与受 CPU 制约的操纵不同,对于后者,任务的完成取决于 CPU 的活动。
受 I/O 制约的操纵的一个典范示例是调用近程服务。在这种情况下,操纵方法将触发请求,然后等待下载任何响应。现实工作将通过另外一台盘算机和另外一个 CPU 以近程方式完成。因此,ASP.NET 线程坚持为等待和闲暇状态。通过异步执行操纵或页面,将处于闲暇状态的线程从等待状态释放出来,从而处置其他传入请求,可以使性能得到晋升。
现实上,其实不是全部耗时较长的操纵都可通过异步操纵晋升性能。耗时较长的内存中盘算不会显著获益于异步实现。它的运行速度甚至可能会更慢一些,因为统一个 CPU 将同时处置 ASP.NET 请求和盘算。此外,您可能仍然须要使用 ASP.NET 线程来现实处置盘算。异步实现受 CPU 制约的操纵,几乎没有什么益处。另外一方面,如果使用近程资源(甚至多个资源),异步方法即使不会提高单个请求的性能,也可大幅提高应用程序的性能。
稍后我将用示例进行说明。当初,我们重点讨论一下在 ASP.NET MVC 中定义和执行异步操纵所需的语法。
认识异步路由
异步路由与同步路由有何不同?在 MVC Futures 中,您须要使用不同的方法来注册同步和异步路由。上面是注册异步路由的旧方法:
routes.MapAsyncRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" } );
您必须使用 MapAsyncRoute 扩展方法,而不是传统同步方法所用的标准 MapRoute。不过,在 ASP.NET MVC 2 RC 中,已消除了这种区分。当初,无论随后将如何执行操纵,注册路由都采用一种方式,即使用 MapRoute 方法。
因此,以常规方式处置请求的 URL,并肯定要使用的控制器类的名称。现实上,须要在派生自新 AsyncController 类的控制器类上定义异步方法,如下所示:
public class TestController : AsyncController { ... }
如果控制器类继承自 AsyncController,则用于将操纵名称映射到方法的约定有些不同。AsyncController 类可以处置同步和异步请求。因此,所使用的约定可识别出方法 Run 和方法 RunAsync,如下所示:
public class TestController : AsyncController { public ActionResult Run(int id) { ... } public void RunAsync(int id) { ... } }
但是,如果执行上述代码,将引发异常(请参见图 3)。
异步操纵通过名称来标识,预期的模式为 xxxAsync,其中 xxx 表示要执行的操纵的默许名称。明显,如果存在另外一个名为 xxx 的方法,并且未使用属性进行辨别,则会引发异常,如图 3 所示。
图 3 操纵名称中的不明确引用
“Async”这个词被视为后缀。用于调用 RunAsync 方法的 URL 仅包括前缀“Run”。例如,以下 URL 将调用方法 RunAsync,将值 5 作为路由参数传递:
http://myserver/demo/run/5
将此操纵解析为同步操纵还是异步操纵,取决于 AsyncController 类中的方法。但是,xxxAsync 方法仅识别操纵的触发器。请求的终结器是控制器类中的另外一个方法,名为 xxxCompleted:
public ActionResult RunCompleted(DataContainer data) { ... }
请注意用于定义异步操纵的这两个方法的不同签名。触发器应为 void 方法。如果将它定义为返回任何值,则返回值将被疏忽。通常,xxxAsync 方法的输入参数须要进行模型绑定。终结器方法通常返回一个 ActionResult 对象,接收一个包括待处置数据的自定义对象并将数据传递给视图对象。要将由触发器盘算的值与终结器声明的参数进行匹配,须要使用一个特别协议。
AsyncController 类
AsyncController 控制器类继承自 Controller,它实现一组新的接口,如下所示:
public abstract class AsyncController : Controller, IAsyncManagerContainer, IAsyncController, IController
异步控制器最奇特的方面是拥有专门的操纵调用程序对象,该对象在底层使用,用于执行操纵。该调用程序须要一个计数器来跟踪操纵的数目,这些操纵形成总体操纵,并且必须在可以声明总体操纵终止之前进行同步。图 4 供给了异步操纵的示例实现。
图 4 简单的异步操纵方法
public void RunAsync(int id) { AsyncManager.OutstandingOperations.Increment(); var d = new DataContainer(); ... // Do some remote work (i.e., invoking a service) ... // Terminate operations AsyncManager.Parameters["data"] = d; AsyncManager.OutstandingOperations.Decrement(); } public ActionResult RunCompleted(DataContainer data) { ... }
AsyncManager 类上的 OutstandingOperations 成员供给一个容器,用于坚持一定数目标挂起异步操纵。它是 OperationCounter 帮助器类的一个实例,供给一个临时 API 进行递增和递加。Increment 方法不限于一元递增,如下所示:
AsyncManager.OutstandingOperations.Increment(2); service1.GetData(...); AsyncManager.OutstandingOperations.Decrement(); service2.GetData(...); AsyncManager.OutstandingOperations.Decrement();
AsyncManager 参数字典用于对值进行分组,以将值作为参数传递给异步调用的终结器方法。在参数字典中,要传递给终结器(前面示例中的 xxxCompleted 方法)的每个参数都应有一个条目。如果字典中的条目都与参数名称不匹配,则参数采用默许值(如果是引用类型,则为 null)。除非实验访问 null 对象,否则不会引发异常。xxxCompleted 方法接受任何受支持类型的参数,使用它们来填充 ViewData 集合或视图识别的任何强类型对象。xxxCompleted 方法担任返回 ActionResult 对象。
是否合适?
总而言之,同步请求是 ASP.NET 中的必要功能,现实上,从 ASP.NET 1.0 开始,就支持异步 HTTP 处置程序。
ASP.NET Web 窗体和 ASP.NET MVC 供给了更高级别的工具,可对异步操纵进行编码,每个工具都有自己的应用程序模型。在 ASP.NET MVC 中,使用的是异步控制器;而在 Web 窗体中,须要使用异步页面。
不过,异步操纵的症结方面是肯定指定的任务是否合适异步实现。只应针对受 I/O 制约的操纵构建异步方法。最后,请注意,异步方法本身不会更快运行,而是使其他请求更快运行。
Dino Esposito 是 Microsoft Press 即将出版的《Programming ASP.NET MVC》的作者,也是《Microsoft .NET:Architecting Applications for the Enterprise》(Microsoft Press,2008 年)的合著者。Esposito 定居于意大利,经常在世界各地的业内活动中发表演讲。您可访问他的博客,网址为 weblogs.asp.net/despos。
衷心感谢以下技巧专家对本文的审视: Stefan Schackow
转自:http://msdn.microsoft.com/zh-cn/magazine/ee336138.aspx
文章结束给大家分享下程序员的一些笑话语录: 看新闻说中国输入法全球第一!领先了又如何?西方文字根本不需要输入法。一点可比性都没有。