使用异步处理程序和 XMLHTTP 处理长时间运行的任务






4.72/5 (30投票s)
2005年5月12日
6分钟阅读

281884

2966
本文将向您展示如何结合使用异步处理程序和 XMLHTTP 在 ASP.NET 中处理长时间运行的任务。
引言
处理长时间运行的任务(例如,SQL 查询执行、Web 服务调用等)一直是高流量 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
接口。该类定义和对象层次结构如下
/*
Class Definition for System.Web.UI.Page
*/
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 中多线程的信息,请参阅“参考”部分。
代码详解
本文使用的示例是一个简单的天气服务。UI 允许用户输入美国邮政编码来查询城市当前的天气状况。我选择 Web 服务调用的原因是它提供了一个绝佳的“真实世界”示例,展示了如何利用 ASP.NET 和 XMLHTTP 的异步功能。
当用户从列表中选择输入的邮政编码并点击“Check Weather”(检查天气)按钮时,会创建两个异步请求:一个发送到天气检查程序处理程序,另一个发送到 Web 服务。使用 DHTML 进度条来“娱乐”用户,同时执行此过程。一旦客户端收到响应,进度条就会停止,UI 也会更新。
此示例背后的主要类是 AsyncTaskHandler
类。此类被映射到一个 .ashx 文件,以便 ASP.NET 可以将其挂接到请求链中。此类实现了两个接口:前面提到的 IHttpAsyncHandler
,以及 System.Web.SessionState.IRequiresSessionsState
接口,该接口用于告知 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 片段返回给客户端。该方法如下所示
/// <summary>
/// Uses the IAsyncResult object to call a web service and writes
/// back the response to the caller
/// </summary>
public void Process()
{
try
{
// Get the zip code from the AsynRequestResult object
string strZip = Result.AsyncState as string;
int zipCode = Int32.Parse(strZip);
// Get the weather information
string message = Result.GetWeather(zipCode);
// Write back to the client
Result.Context.Response.Write(message);
}
finally
{
// Tell ASP.NET that the request is complete
Result.Complete();
}
}
在 AsyncRequestResult.GetWeather
方法下,对天气 Web 服务的实际调用如下所示
///<summary>
/// Gets the current weather information based on a US Zip code
/// </summary>
/// <param name="zipCode">City zip code</param>
/// <returns>Location name along with current temperature.
/// Empty string otherwise.</returns>
public string GetWeather(int zipCode)
{
string message = "";
try
{
// Call the web service
ExtendedWeatherInfo info =
weatherService.GetExtendedWeatherInfo(zipCode);
// Format the message
message = string.Format("<h2>{0} - {1}" +
"</h2>Current Temperature: {2}a<br>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 发出完整的异步请求,并附加一个回调函数
/*
Creates a XMLHTTP async-call to an ASP.NET async handler
*/
function postRequest(url)
{
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
// 'true' specifies that it's a async call
xmlhttp.Open("POST", url, true);
// Register a callback for the call
xmlhttp.onreadystatechange =
function ()
{
if (xmlhttp.readyState == 4)
{
var response = xmlhttp.responseText;
divResponse.innerHTML += "<p>" + response + "</p>";
stopProgressBar();
}
}
// Send the actual request
xmlhttp.Send();
}
从这里开始,在我们等待异步处理程序执行长时间处理请求的同时,我们向用户显示一个简单的进度条,让他们知道他们的请求正在被处理。
以下图表显示了 XMLHTTP 请求的执行过程
- 当用户点击“Check Weather”(检查天气)按钮时,会创建一个
XMLHTTP
对象。 - 此对象向 weatherchecker.ashx 发出异步(非阻塞)请求。
- 一个临时回调函数被附加到该对象上,用于处理来自处理程序的响应。
- 调用另一个 JavaScript 函数来启动进度条并显示“Processing request...”文本。
- 每当处理程序完成对 Web 服务的调用时,就会生成一个响应并发送回客户端的临时回调函数。
- 临时回调函数在客户端执行,响应文本被设置为一个
<div>
,进度条停止。
此过程的最终结果如下所示
结论
能够异步执行长时间运行的任务对于任何 Web 应用程序(无论大小)来说都是巨大的好处。我希望本文能为开发人员提供一种解决此设计问题的替代方法。此外,我希望开发人员在决定向 ASP.NET 应用程序添加多线程时能更加谨慎。
请随时使用下面的论坛对本文提出任何意见或建议。
参考
在撰写本文的过程中,我参考了以下资源
- Fritz Onion 撰写的《在服务器端 Web 代码中使用线程并构建异步处理程序》。
- ejse 提供的《天气服务》。