ASP.NET 异步请求处理(Asynchronous Http Handlers)

ASP.NET中你可以通过继承IHttpHandler这个接口来实现一个同步(Synchronous)处理用户请求的类。比如你希望对于一切类型为fakephp的请求都通过你的Http Hanlder来处理,你可以实现以下这个类:

  
    
using System;
using System.Web; public class FakePHPHttpHandler : IHttpHandler {
public void ProcessRequest(HttpContext context) {
// let's pretend we can handle PHP stuff
} public bool IsReusable {
get { return true ; }
}
}

然后通过在IIS里将你的dll注册为.fakephp的handler就可以了。这些在以下的MSDN文档里都有介绍:

http://msdn2.microsoft.com/en-us/library/system.web.ihttphandler.aspx

这里想说的是如何实现一个异步(Asynchronous)d的HTTP Handler。说起来其实也简单,只要实现IHttpAsyncHandler这个接口就好了。

IHttpAsyncHandler有两个method:

Public method BeginProcessRequest
Initiates an asynchronous call to the HTTP handler.

Public method EndProcessRequest
Provides an asynchronous process End method when the process ends.

这显然是和.NET Framework中标准的Asynchronous Programming Model (或者叫“异步模式”, Asynchronous Pattern)是一致的:

参考: Asynchronous Programming Overview

异步模式的优势是ASP.NET的worker thread不会等待BeginProcessRequest返回而是会掉头去接收其他的用户请求(当然你也可以要求worker thread等待,不过这样就等于变成了Synchronous Handler)。因为通常处理一个web request的后台时间需要比较长。假设你每秒只能处理10个用户请求,在同步模式下如果有11个人同时访问你的服务,就有一个人会看到500 Internal Server Error之类的错误消息了。但如果是异步模式,worker thread只要调用BeginProcessRequest,而根据Asynchronous Programming Overview,BeginProcessRequest应该立刻返回(“立刻”的含义是它不应该进行长时间的操作,而应该调用QueueUserWorkItem之类的API将耗时的任务放到新线程里执行),这样worker thread就可以腾出手去接收下一个user request了。

注:ASP.NET的max worker thread上限可以通过processModel configuration element里的maxWorkerThreads属性来改变(参考:Improving ASP.NET Performance 以及 processModel element),但最大的值也只是100 (range from 5 to 100, default 20)。

由此引出的问题自然是:当异步操作完成时, ASP.NET是如何知道并做相应处理的。这有一下几种选择:

1) 当调用BeginProcessRequest的时候,ASP.NET可以同时传入一个AsyncCallback的delegate,而在你完成异步操作后,你应该调用这个回调函数来通知ASP.NET。

2) ASP.NET可以不停地查看IAsyncResult (这个是BeginProcessRequest的返回值)IsCompleted属性来确认异步操作是否已经完成了,当然,当你完成异步操作时,你有义务将IsCompleted设成true。

3) ASP.NET也可以等待AsyncWaitHandle的信号,AsyncWaitHandle是IAsyncResult的另一个属性,这个和经典的Win32里waiting on kernel object是类似的。

4) ASP.NET可以直接调用EndProcessRequest。

注意:3) 和 4) 是Asynchronous Programming Overview里规定的标准的blocking execution的方式,也就是说,如果你的主线程在异步操作完成前无法再做任何工作时,它可以通过3) 或者 4)来等待异步操作的完成。

从理论上来说,你应该保证你的IHttpAsyncHandler能满足以上所有4种方式。但现实中,你未必一定如此做。那么哪些是我们必须实现以匹配ASP.NET的要求的呢?或者ASP.NET究竟是如何实现异步调用及返回的呢?

事实上,ASP.NET采用了方法1,也就是说,在调用BeignProcessRequest的时候,ASP.NET传入了一个AsyncCallback,而你应该在完成异步操作后调用这个callback,而在这个AsyncCallback里,ASP.NET又调用了你的EndProcessRequest来做收尾工作。

根据上面的讨论,我们可以如下设计我们的Asynchronous Http Hanlder:

  
    
public class AsyncFakePHPHttpHandler : IHttpAsyncHandler
{
private void ProcessRequestCallback( object state)
{
AsyncResult async
= (AsyncResult)state;
// this is where the real work is done
ProcessRequest(async.context);
async.isCompleted
= true ;
if ( null != async.asyncCallback)
async.asyncCallback(async);
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback asyncCallback, object state)
{
AsyncResult async
= new AsyncResult(context, asyncCallback, state);
// if the callback is null, we can return immediately and let EndProcessRequest do all the job
// if callback is not null, we will use our thread pool to execute the necessary asynchronous operations
// what happens in ASP.NET is that the callback in NOT null, so QueueUserWorkItem will be used
if ( null != async.asyncCallback)
threadPool.QueueUserWorkItem(ProcessRequestCallback, async);
return async;
}
// this design also satisfies method 4), we implement it this way to follow the Asynchronous Pattern as much as we can
public void EndProcessRequest(IAsyncResult result)
{
AsyncResult async
= (AsyncResult)result;
if ( null == async.asyncCallback)
ProcessRequest(async.context);
}
}

总结一下实现异步Http Handler的要点:

1) 所有在BeginProcessRequest中的耗时操作(比如IO什么的)都应该采用异步调用(比如BeginRead/EndRead)或者生成新的线程去执行。不错,你可以设计一个blocking BeginProcessRequest,没有人能阻止你这么做。But that's a BAD BAD idea.

2) 实现BeginProcessRequest/EndProcessRequest的目的是允许ASP.NET来异步调用你的Http Handler

3) 你应该创建一个实现IAsyncResult接口的类,在BeginProcessRequest中你会生成一个该类的实例并返回给ASP.NET(注意BeginProcessRequest的返回值类型)。而根据Asynchronous Pattern,ASP.NET在调用EndProcessRequest的时候会把这个实例再传回给你,你可以用这个实例来判断你所执行的任务的当前状态。

4) 我个人感觉比较容易导致困惑的是这里“两段式”的异步调用操作。首先ASP.NET是通过BeginProcessRequest/EndProcessRequest来异步调用我们的Http Handler的。然后我们在BeginProcessRequest又再次用异步模式(用QueueUserWorkItem或者其他的Begin*/End*操作)去完成真正的工作。实际上第二步的异步调用才是真正生成另一个thread来处理工作的地方。ASP.NET调用我们的BeginProcessRequest只是一种形式上的协议通知,因为是我们告诉ASP.NET:Hey,我是一个异步的handler。ASP.NET说:那好吧,既然你这么说的,我就用你异步的接口来调用你。事实上,在HttpRuntime的源代码中,可以看到ASP.NET的操作如下:

                if (app is IHttpAsyncHandler) {
                    // asynchronous handler
                    IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
                    context.AsyncAppHandler = asyncHandler;
                    asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
                }
                else {
                    // synchronous handler
                    app.ProcessRequest(context);
                    FinishRequest(context.WorkerRequest, context, null);
                }

====================================================================

最后的题外话,关于IIS/ASP.NET处理请求的工作流也是一个很有趣的问题,下面的这篇文章很棒(but the author's English writing kinda sucks, : ) )。比如,从中我们可以知道,整个流程中其实牵涉了两个Queue,一个是Kernel mode下的,一个是User mode下的,而后者就是ASP.NET所使用的Application Queue,而ASP.NET的worker thread就是从这个Queue里去取下一个需要处理的请求的。

http://blog.joycode.com/demonfox/archive/2007/11/14/111369.joy

你可能感兴趣的:(asp.net)