ASP.NET异步处理与XMLHTTP应用

在高需求的Web应用程序中,保持客户端UI的响应性一直是其重要特性之一。许多网站,如Expedia、Travelocity等,使用中间的“请稍候...”页面来解决这个问题。而像Google Maps这样的网站则使用客户端特性,例如XMLHTTP,来在不移动用户当前页面的情况下进行请求。本文的目的是展示如何结合使用XMLHTTP和ASP.NET,以使最终用户体验更加可接受。

从架构上讲,ASP.NET的一个关键特性是其对象具有短暂的生命周期。这对于高需求网站来说非常重要,因为它允许应用程序快速高效地执行,同时还能保持高水平的并发用户。保持ASP.NET网站响应性的关键因素是了解控制页面执行的框架结构。

许多开发者都知道,在ASP.NET中创建一个简单的网页需要使用System.Web.UI.Page类。然而,有些人可能不知道还有其他方式来处理对应用程序的请求。这就是System.Web.IHttpHandler和System.Web.IHttpAsyncHandler接口发挥作用的地方。

IHttpHandler和IHttpAsyncHandler快速概览

ASP.NET中所有的请求执行都是通过System.Web.IHttpHandler接口进行的。如果查看System.Web.UI.Page类的类图,会看到它继承自System.Web.UI.TemplateControl并实现了System.Web.IHttpHandler接口。类定义和对象层次结构如下:

public class Page : TemplateControl, IHttpHandler

IHttpAsyncHandler接口继承自IHttpHandler,并定义了两个额外的方法,BeginProcessRequest和EndProcessRequest。这些方法构成了在ASP.NET中处理异步调用所需的机制。

对于这种场景,最好使用IHttpAsyncHandler而不是Page对象,因为Page对象每个请求都会执行额外的工作(子控件渲染、事件处理通知等)。有关IHttpAsyncHandler的更多信息,请参见MSDN文档。

ASP.NET中的线程处理简易指南

在ASP.NET应用程序中实现线程有三种简单的方法:

  • 使用System.Threading.ThreadPool。
  • 使用自定义委托并调用其BeginInvoke方法。
  • 使用System.Threading.Thread类创建自定义线程。

前两种方法为应用程序提供了快速启动工作线程的方式。但不幸的是,它们会损害应用程序的整体性能,因为它们消耗了ASP.NET用于处理HTTP请求的线程池中的线程。换句话说,如果在一个请求中调用了五个池/委托线程,应用程序将只剩下20个可用线程(默认情况下,ASP.NET为每个应用程序提供25个工作线程)。解决这个问题的方法是使用第三种选项,因为创建的线程不属于应用程序池。

如果想更多地了解ASP.NET中的线程处理,请参见参考资料部分。

代码步骤

本文使用的例子是一个简单的天气服务。用户界面允许用户输入美国邮政编码以检查城市当前的天气状况。选择Web服务调用的原因是它提供了一个关于如何使用ASP.NET的异步功能和XMLHTTP的“现实世界”示例。

当用户从列表中选择一个输入的邮政编码并点击“检查天气”按钮时,会创建两个异步请求,一个发送给天气检查处理器,另一个发送给Web服务。使用DHTML进度条在执行此过程时让用户“娱乐”。一旦客户端收到响应,进度条就会停止,UI也会更新。

这个示例背后的主要类是AsyncTaskHandler类。这个类映射到一个.ashx文件,以便ASP.NET可以连接请求链。这个类实现了两个接口,前面提到的IHttpAsyncHandler和System.Web.SessionState.IRequiresSessionState接口,后者用于告诉ASP.NET这个处理器可以访问会话状态。然后,这个类使用两个类AsyncRequestResult和AsyncRequest来处理请求。

AsyncRequestResult实现了System.IAsyncResult接口,以便ASP.NET可以调用执行回调所需的适当方法。AsyncRequest类用于调用外部天气Web服务并将响应写回等待的客户端。对象之间的协作可以通过以下图表看到:

处理器一收到请求,就创建一个AsyncRequestResult类型的对象。使用这个对象作为构造函数参数,处理器然后创建一个AsyncRequest类型的对象。最后,处理器创建一个System.Thread.Thread类型的工作线程,并使用AsyncRequest的Process方法,并将创建的AsyncRequestResult返回给ASP.NET和客户端。

大部分核心逻辑发生在AsyncRequest.Process方法中。在这个方法中,使用相关的AsyncRequestResult对象调用天气信息Web服务,并将HTML片段返回给客户端。该方法如下所示:

public void Process() { try { // 从AsyncRequestResult对象获取邮政编码 string strZip = Result.AsyncState as string; int zipCode = Int32.Parse(strZip); // 获取天气信息 string message = Result.GetWeather(zipCode); // 写回客户端 Result.Context.Response.Write(message); } finally { // 告诉ASP.NET请求已完成 Result.Complete(); } }

AsyncRequestResult.GetWeather方法下对天气Web服务的实际调用如下所示:

public string GetWeather(int zipCode) { string message = ""; try { // 调用Web服务 ExtendedWeatherInfo info = weatherService.GetExtendedWeatherInfo(zipCode); // 格式化消息 message = string.Format("

{0} - {1}

Current Temperature: {2}a
Feels Like: {3}.", zipCode.ToString(), info.Info.Location, info.Info.Temprature, info.Info.FeelsLike); } catch { message = "An error was encountered while calling web service."; } return message; }

现在已经对代码的工作方式有了快速的了解,让看看客户端如何创建请求并处理响应。

在IE中使用XMLHTTP

要通过JavaScript以编程方式进行HTTP请求,需要使用XMLHTTP。很长一段时间以来,微软在其MSXML解析器中捆绑了一个版本的这项技术。要创建一个绑定到此功能的JavaScript对象,请执行以下操作:

var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");

这个函数将对URL进行完整的异步请求,并附加一个回调函数:

function postRequest(url) { var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // 'true'指定这是一个异步调用 xmlhttp.Open("POST", url, true); // 为调用注册回调 xmlhttp.onreadystatechange = function() { if(xmlhttp.readyState == 4) { var response = xmlhttp.responseText; divResponse.innerHTML += "

" + response + "

"; stopProgressBar(); } }; // 发送实际请求 xmlhttp.Send(); }

从这里开始,在等待异步处理器执行长时间处理请求的同时,向用户显示一个简单的进度条,告诉他们他们的请求正在处理中。

以下图表显示了XMLHTTP请求的执行过程:

  • 当用户点击“检查天气”按钮时,创建一个XMLHTTP对象。
  • 这个对象向weatherchecker.ashx创建一个异步(非阻塞)请求。
  • 将一个临时回调附加到对象上,以处理来自处理器的响应。
  • 调用另一个JavaScript函数启动进度条并显示“处理请求...”文本。
  • 每当处理器完成对Web服务的调用时,就会生成一个响应返回给客户端的临时回调。
  • 临时回调在客户端执行,将响应文本设置为
    ,并停止进度条。

这个过程的最终结果如下:

能够异步执行长时间运行的任务对于任何规模的Web应用程序来说都是一个巨大的好处。希望本文能向开发者展示解决这个设计问题的另一种方式。同时,希望开发者在为ASP.NET应用程序添加线程时更加注意。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485