ASP.NET页面剖析-异步页面
ASP.NET页面会被HTTP处理程序作为Page类的实例处理。每个请求会占用ASP.NET线程池中的一个线程,在请求完毕后该线程才会被释放。如果被请求的页面频繁地启动外部的、高耗时的任务时,经常会出现ASP.NET进程闲置,但池中没有空闲的线程来处理新入的其他页面的请求。这种情况下,创建异面页面可以减轻这个问题。
异步ASP.NET页面的构建涉及两个方面: @Page指令的一个新属性Async,以及注册若干异步执行的任务。异步任务可以通过两种途径注册:一种是为PreRenderComplete事件定义异步处理程序Begin/End对(AddOnPreRenderCompleteAsync方法用于将异步事件处理程序添加到页面的PreRenderComplete事件中);另一种是创建代表异步任务的PageAsyncTask对象,PageAsyncTask类代表要以异步方式执行任务(RegisterAsyncTask方法能够接受一个PageAsyncTask对象,返回void)。
AddOnPreRenderCompleteAsync和RegisterAsyncTask的区别:
从功能上讲,这两个方法几乎相同,二者都会将请求的执行分为两部分——分别在同步点前后。他们之间的区别在于:第一个区别在逻辑上,RegisterAsyncTask是一个用于在一个页面中运行多个异步任务的API(而不能跨越多个带有Async=true属性的异步页面),而AddOnPreRenderCompleteAsync是专门为多个异步页面设计的。RegisterAsyncTask执行End处理程序时,所处线程的上下文比AddOnPreRenderCompleteAsync所处线程的更丰富。该线程上下文包括模拟(impersonation)和HTTP上下文信息,该信息在一般异步页面的End处理程序所处线程中不存在。此外,RegisterAsyncTask还允许设置超时值,确保任何任务消耗的时间不会超过指定的秒数。另一个区别在:RegisterAsyncTask有利于实现对远程源的多个调用。我们只需设置一个布尔类型的标志,便会获得并行执行的能力,而不必要自行创建并管理自定义的IAsyncResult对象。
前台代码示例:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="StreamDownload.aspx.cs" Inherits="WebTestDemo.AsyncPage.StreamDownload" Async="true" Trace="true"%> 2 3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4 5 <html xmlns="http://www.w3.org/1999/xhtml"> 6 <head runat="server"> 7 <title></title> 8 </head> 9 <body> 10 <form id="form1" runat="server"> 11 <div> 12 <%=msdnData%> 13 </div> 14 </form> 15 </body> 16 </html>
注意:以上@Page指令的Async和Trace属性。Async属性使页面能够为PreRenderComplete事件注册异步处理程序,Trace属性使页面能够显示页面跟踪信息。
后台代码示例:
1 /// <summary> 2 /// 异步下载MSDN首页实例 3 /// </summary> 4 public partial class StreamDownload : System.Web.UI.Page 5 { 6 const string connectionToMSDN = "http://msdn.microsoft.com"; 7 private WebRequest req; 8 public string msdnData; 9 10 protected override void OnInit(EventArgs e) 11 { 12 //将跟踪消息写入页面跟踪日志 13 Trace.Warn("当前线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); 14 } 15 16 protected void Page_Load(object sender, EventArgs e) 17 { 18 //AddOnPreRenderCompleteAsync方法 19 AddOnPreRenderCompleteAsync( 20 new BeginEventHandler(BeginTask), 21 new EndEventHandler(EndTask)); 22 23 //RegisterAsyncTask方法 24 //PageAsyncTask task = new PageAsyncTask( 25 // new BeginEventHandler(BeginTask), 26 // new EndEventHandler(EndTask), 27 // null, 28 // null); 29 //RegisterAsyncTask(task); 30 } 31 32 IAsyncResult BeginTask(object sender, EventArgs e, AsyncCallback cb, object state) 33 { 34 Trace.Warn("开始异步 当前线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); 35 //准备一个Web请求 36 req = WebRequest.Create(connectionToMSDN); 37 //开始操作并返回IAsyncResult对象 38 return req.BeginGetResponse(cb, state); 39 40 } 41 42 void EndTask(IAsyncResult ar) 43 { 44 //这段代码将调用一个池里的线程 45 string text; 46 using (WebResponse response = req.EndGetResponse(ar)) 47 { 48 StreamReader reader; 49 using (reader = new StreamReader(response.GetResponseStream())) 50 { 51 text = reader.ReadToEnd(); 52 } 53 54 msdnData = ProcessFeed(text); 55 } 56 Trace.Warn("结束异步 当前线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); 57 } 58 59 protected override void OnSaveStateComplete(EventArgs e) 60 { 61 Trace.Warn("当前线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); 62 } 63 64 string ProcessFeed(string feed) 65 { 66 //来自XML输入,创建页面输出 67 return Server.HtmlDecode(feed); 68 } 69 }
图1:从MSDN页面下载的内容
图2:跟踪的请求详细信息
图2能看出,当页面加载到达PreRenderComplete阶段前,异步页面会一直执行,随后被阻塞,等待异步操作完成。当该操作最终完成后,页面会从PreRenderComplete阶段继续执行。根据ASP.NET设计,在其中有一个针对异步操作的“展开点”(unwind point)(也称为“同步点”[async point]),该点位于PreRender和PreRenderComplete事件之间。当页面接收到PreRender事件时,该同步点尚未到达。而页面接收到PreRenderComplete时,刚好超过该同步点。
当程序处理到PreRender之后时,线程会被释放,执行外部的任务, 异步任务执行完成后,HTTP运行库会再次处理请求,即从PreRenderComplete阶段开始继续完成其页面生命周期的其他阶段(如图2中的后续步骤)。