65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (30投票s)

2005年5月12日

6分钟阅读

viewsIcon

281884

downloadIcon

2966

本文将向您展示如何结合使用异步处理程序和 XMLHTTP 在 ASP.NET 中处理长时间运行的任务。

Sample Image - main.jpg

引言

处理长时间运行的任务(例如,SQL 查询执行、Web 服务调用等)一直是高流量 Web 应用程序的重要功能。这些应用程序面临的一个问题是在任务执行期间保持客户端 UI 的响应性。

许多网站,如 Expedia、Travelocity 等,使用中间的“请稍候...”页面来解决这个问题。而像 Google Maps 这样的网站则使用客户端功能,如 XMLHTTP,在不让用户离开当前页面的情况下发出此类请求。本文旨在说明如何结合使用 XMLHTTP 和 ASP.NET,让最终用户体验稍微好一些。

背景

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

许多开发人员知道,要在 ASP.NET 中创建一个简单的网页,必须使用 System.Web.UI.Page 类。然而,其中一些人可能不知道还有其他方法可以处理应用程序的请求。这时 System.Web.IHttpHandlerSystem.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

System.Web.UI.Page hierarchy

IHttpAsyncHandler 接口继承自 IHttpHandler,并定义了两个额外的方法,分别是 BeginProcessRequestEndProcessRequest。这两个方法构成了在 ASP.NET 中处理异步调用的机制。

对于这种情况,使用 IHttpAsyncHandler 比使用 Page 对象更好,因为 Page 会为每个请求执行许多额外的操作(子控件渲染、事件处理程序通知等)。有关 IHttpAsyncHandler 的更多信息,请参阅 MSDN 文档

ASP.NET 中多线程的简易指南

您可以通过三种简单的方法在 ASP.NET 应用程序中实现多线程:

  1. 使用 System.Threading.ThreadPool
  2. 使用自定义委托并调用其 BeginInvoke 方法。
  3. 借助 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 此处理程序可以访问会话状态。然后,此类使用 AsyncRequestResultAsyncRequest 这两个类来处理请求。

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

Async request object collaboration

  1. 处理程序一接收到请求,就会创建一个 AsyncRequestResult 类型的对象。
  2. 处理程序使用此对象作为构造函数参数,然后创建一个 AsyncRequest 类型的对象。
  3. 最后,处理程序创建一个 System.Thread.Thread 类型的用户线程,并使用 AsyncRequestProcess 方法,然后将创建的 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();
}

从这里开始,在我们等待异步处理程序执行长时间处理请求的同时,我们向用户显示一个简单的进度条,让他们知道他们的请求正在被处理。

Making the UI responsive while executing a async call

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

Full request to handler and UI response

  1. 当用户点击“Check Weather”(检查天气)按钮时,会创建一个 XMLHTTP 对象。
  2. 此对象向 weatherchecker.ashx 发出异步(非阻塞)请求。
  3. 一个临时回调函数被附加到该对象上,用于处理来自处理程序的响应。
  4. 调用另一个 JavaScript 函数来启动进度条并显示“Processing request...”文本。
  5. 每当处理程序完成对 Web 服务的调用时,就会生成一个响应并发送回客户端的临时回调函数。
  6. 临时回调函数在客户端执行,响应文本被设置为一个 <div>,进度条停止。

此过程的最终结果如下所示

Result from call

结论

能够异步执行长时间运行的任务对于任何 Web 应用程序(无论大小)来说都是巨大的好处。我希望本文能为开发人员提供一种解决此设计问题的替代方法。此外,我希望开发人员在决定向 ASP.NET 应用程序添加多线程时能更加谨慎。

请随时使用下面的论坛对本文提出任何意见或建议。

参考

在撰写本文的过程中,我参考了以下资源

  • Fritz Onion 撰写的《在服务器端 Web 代码中使用线程并构建异步处理程序》。
  • ejse 提供的《天气服务》。
© . All rights reserved.