ASP.NET异步请求处理(Asynchronous Http Handlers)
2008-01-05 16:45
423 查看
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:
BeginProcessRequest
Initiates an asynchronous call to the HTTP handler.
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里去取下一个需要处理的请求的。
Dissection of an ASP.NET 2.0 request processing flow
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:
BeginProcessRequest
Initiates an asynchronous call to the HTTP handler.
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里去取下一个需要处理的请求的。
Dissection of an ASP.NET 2.0 request processing flow
相关文章推荐
- ASP.NET异步请求处理(Asynchronous HTTP Handlers)
- ASP.NET异步请求处理(Asynchronous HTTP Handlers)
- ASP.NET WebForm 之 Ajax 请求后端处理 概述 ASP.NET 在MVC中的用途非常广泛,操作起来也非常简单。前台请求异步请求 Controlle
- ASP.Net:异步请求分页示例:Handler.ashx + $.ajax() + Json + 分页处理
- 【C#】对异步请求处理程序IHttpAsyncHandler的理解和分享一个易用性封装 【手记】走近科学之为什么明明实现了IEnumerable<T>的类型却不能调用LINQ扩展方法 【手记】手机网页弹出层后屏蔽底层的滑动响应 【手记】ASP.NET提示“未能创建类型”处理 【Web】一个非常简单的移动web消息框 【手记】解决EXCEL跑SQL遇“查询无法运行或数据库表无法打开...”
- 有关Asp.net 中数据请求的处理的新认知:利用httpHandlers
- ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
- IIS是如何处理ASP.NET请求的
- Asp.net请求处理之 管道处理
- ASP.NET MVC 文件异步上传问题处理
- Asp.Net 请求处理过程(IIS 6)
- ASP.NET页面运行机制以及请求处理流程
- IIS5、IIS6、IIS7的ASP.net 请求处理过程比较
- IIS5、IIS6、IIS7的ASP.net 请求处理过程比较
- 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server
- 各版本IIS下ASP.net请求处理过程分析第1/3页
- ASP.Net请求处理模式
- IIS处理Asp.net请求和Asp.net页面生命周期说明
- IIS5、IIS6、IIS7的ASP.net 请求处理过程比较
- ASP.NET之自定义异步HTTP处理程序(图文教程)