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裡去取下一個需要處理的請求的。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/blackvii/archive/2008/01/05/2026903.aspx