與 ASPNET 相比ASPNET 的各方面改進可以說是非常巨大的但就其實現層面來說最大的增強莫過於提供了對異步 頁面的支持通過此機制編寫良好的頁面可以將數據庫WebService 調用等慢速操作對網站吞吐能力的影響降到最低並極大的改善網站的平均頁 面響應速度本文將從使用和實現兩個層面簡單的剖析這一強大機制的原理以便讀者能夠更好的應用這一機制
對一個網頁請求的生命周 期來說首先是 Web 服務器收到客戶端 HTTP 請求將請求轉交給 ASPNET 引擎引擎將以流水線方式調用合適的 Web 應用程序和最 終的頁面進行處理頁面會根據請求內容執行某些後台操作如訪問數據庫調用遠程 WebService 等等最終將結果以某種可視化形式展示到最 終用戶的浏覽器中
而為了提高響應速度和吞吐量現代的 Web 服務器往往會將 Web 應用程序和頁面放在一個緩沖池中備用 避免每次處理請求時重建環境而請求到來時Web 服務器會從一個系統線程池中獲取臨時線程調用從 Web 應用程序和頁面緩沖池中獲取的處理實例 完成對請求的處理並最終返回處理的結果
咋一看這種機制非常完美能夠最大限度的重用系統資源但實際上其中存在著很大的優化余 地
我們可以將一個頁面請求的處理過程進一步細化為下面步驟
Web 服務器接受請求並由 引擎轉發請求
頁面處理請求訪問數據庫調用遠程 WebService 等
頁面將處理結果以某種形 式展現如 HTML 表格等
Web 服務器將結果返回給最終客戶的浏覽器
其中第一步涉及到核心態 網絡驅動需要頻繁切換回用戶態以將請求轉交給處理引擎這裡涉及到大量的核心態和用戶態切換為減少這個負擔IIS 開始提供 了 httpsys 在核心態直接對大多數請求進行處理這是 MS 在中間件一級就已經替我們做好的優化我們無需也無法關心
而 另外一個潛在的優化點就是異步頁面的目標增強頁面處理請求的並發性
Web 服務器在從線程池獲取臨時線程後在線程中調用頁面相 關代碼處理請求而這裡的請求處理過程往往涉及到較為緩慢的操作例如訪問數據庫調用遠程 WebService 等如果數據庫是在本機的話還好系 統 CPU 時間只是從處理線程移交到後台數據庫線程而一旦處理運算邏輯在遠程例如訪問外部獨立數據庫或調用 WebService 完成某種操 作此時此線程就只能無謂的等待操作結束而作為 Web 服務器處理客戶端請求的線程池其最大容納線程的數量肯定是有限的(雖然大多數情況下這個上 限值可以修改例如 ASPNET 中可以通過修改 nfig 的 processModel 標簽調整最大數量缺省)一 旦超過此數量的請求正在並行執行或者說正在等待後台慢速的操作此時新來的請求就會因為處理請求線程池中無可用線程出現雖然 CPU 負荷非常低但 仍出現 服務器不可用 類似的錯誤從而事實上造成對應用的 DoS(拒絕服務)攻擊即使此上限設置很大也會因為大量等待操作降低其它 本可以快速的頁面的響應速度
要處理這種情況雖然可以通過繼續增大請求處理線程池最大容量緩解但總是治標不治本更好的 方法就是將請求處理和頁面處理分離避免慢速頁面處理占用快速請求處理的時間頁面在接受到引擎的處理頁面請求後通過調用異步方法來試圖完成實際頁面處 理處理結果從單獨線程池獲取線程進行監控而發送頁面請求的請求處理線程將被直接釋放以便繼續處理其它的頁面請求這也就是 ASPNET 異步頁 面的基本思路實際上這個思路在 ASPNET 時就已經提出Fritz Onion 曾於 年在 MSDN 雜志發表過一篇文章 詳細討論這個問題並給出了一個簡單的解決方案
Use Threads and Build Asynchronous Handlers in Your ServerSide Web Code
文 中提供的實現很好的對此問題進行原理上的驗證但從實現角度較為繁瑣需要自行處理 IAsyncResult 接口以及自定義線程池而且缺少 對 HTTP 上下文以及超時等的處理
好在 ASPNET 對此問題提供了內建的支 持Jeff Prosise 在 MSDN 雜志的文章中詳細的討論了其實現思路和使用方法
Asynchronous Pages in ASPNET
從使用角 度來說異步頁面的支持非常透明使用者只需要在頁面定義的 Page 標簽中指定異步模式例如
<%@ Page Async=true %>
然 後就可以在 Page 的實現代碼中通過 Page 類型 的 AddOnPreRenderCompleteAsync 或 PageAsyncTask 方法提交異步的頁面處理代碼ASPNET 引擎會 根據頁面的異步模式設定調用合適的頁面處理開始和結束方法
對大多數簡單的異步處理情況可以直接調 用 AddOnPreRenderCompleteAsync 方法提交頁面請求開始和結束時的處理代碼例如上述文章中給出的一個內部處 理 HTTP 頁面請求的例子
// AsyncPageaspxcs
using System;
using SystemWeb;
using SystemWebUI;
using SystemWebUIWebControls;
using SystemNet;
using SystemIO;
using SystemText;
using SystemTextRegularExpressions;
public partial class AsyncPage : SystemWebUIPage
{
private WebRequest _request;
void Page_Load (object sender EventArgs e)
{
AddOnPreRenderCompleteAsync (
new BeginEventHandler(BeginAsyncOperation)
new EndEventHandler (EndAsyncOperation)
);
}
IAsyncResult BeginAsyncOperation (object sender EventArgs e
AsyncCallback cb object state)
{
_request = WebRequestCreate();
return _requestBeginGetResponse (cb state);
}
void EndAsyncOperation (IAsyncResult ar)
{
string text;
using (WebResponse response = _requestEndGetResponse(ar))
{
using (StreamReader reader =
new StreamReader(responseGetResponseStream()))
{
text = readerReadToEnd();
}
}
Regex regex = new Regex (href\\s*=\\s*\([^\]*)\
RegexOptionsIgnoreCase);
MatchCollection matches = regexMatches(text);
StringBuilder builder = new StringBuilder();
foreach (Match match in matches)
{
builderAppend (matchGroups[]);
builderAppend(<br/>);
}
OutputText = builderToString ();
}
}
AsyncPage 頁面的 OnLoad 事件中 提交異步處理方法ASPNET 引擎會在頁面加載完成後調用 BeginAsyncOperation 方法啟動異步方法這裡的異步請求是打開一 個遠程 Web 頁面而大多數諸如數據庫WebService 調用等等都提供了類似的異步調用版本頁面處理開始方法會返回異步調用請求 的 IAsyncResult 封裝通過此接口檢測處理的完成情況而在 BeginAsyncOperation 方法返回之後處理連接請求的線程 將回到線程池用來處理後續的連接請求直到實際的異步處理操作完成例如 Web 頁面被取回引擎才會從獨立線程池中獲取臨時線程調 用 EndAsyncOperation 方法完成後續的操作
而 PageAsyncTask 的方式則是增強版本除了異步頁面處理開始和結束方法自 身外還可以提供在超時情況下的處理方法以及處理時的狀態對象上述文章中給出的對應例子如下
// AsyncPageTaskaspxcs
using System;
using SystemWeb;
using SystemWebUI;
using SystemWebUIWebControls;
using SystemNet;
using SystemIO;
using SystemText;
using SystemTextRegularExpressions;
public partial class AsyncPageTask : SystemWebUIPage
{
private WebRequest _request;
protected void Page_Load(object sender EventArgs e)
{
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(BeginAsyncOperation)
new EndEventHandler(EndAsyncOperation)
new EndEventHandler(TimeoutAsyncOperation)
null
);
RegisterAsyncTask(task);
}
IAsyncResult BeginAsyncOperation(object sender EventArgs e
AsyncCallback cb object state)
{
_request = WebRequestCreate();
return _requestBeginGetResponse(cb state);
}
void EndAsyncOperation(IAsyncResult ar)
{
string text;
using (WebResponse response = _requestEndGetResponse(ar))
{
using (StreamReader reader =
new StreamReader(responseGetResponseStream()))
{
text = readerReadToEnd();
}
}
Regex regex = new Regex(href\\s*=\\s*\([^\]*)\
RegexOptionsIgnoreCase);
MatchCollection matches = regexMatches(text);
StringBuilder builder = new StringBuilder();
foreach (Match match in matches)
{
builderAppend(matchGroups[]);
builderAppend(<br/>);
}
OutputText = builderToString();
}
void TimeoutAsyncOperation(IAsyncResult ar)
{
OutputText = Data temporarily unavailable;
}
}
為驗證這一機制的實現效果我們可以在各個 方法的入口處設置斷點因為 VS 的 IDE 屏蔽了底層 CLR 實現信息我們需要在 Debug\Windows \Immediate Window 窗口中輸入 load sos 命令加載 CLR 調試支持具體的 sos 命令可以輸入 !help 查詢 幫助或參考我以前《用WinDbg探索CLR世界》的系列文章這裡不再羅嗦
在 AsyncPage 類型 的 Page_LoadBeginAsyncOperation 和 EndAsyncOperation 方法中分別輸入 !ClrStack 命 令可以獲取當前線程的調用堆棧
!clrstack
OS Thread Id: xb ()
ESP EIP
dd ac AsyncPagePage_Load(SystemObject SystemEventArgs)
df dd SystemWebHttpRuntimeProcessRequest(SystemWebHttpWorkerRequest)
!clrstack
OS Thread Id: xb ()
ESP EIP
dc ad AsyncPageBeginAsyncOperation(SystemObject SystemEventArgs SystemAsyncCallback SystemObject)
df dd SystemWebHttpRuntimeProcessRequest(SystemWebHttpWorkerRequest)
!clrstack
OS Thread Id: xd ()
ESP EIP
ceee feff AsyncPageEndAsyncOperation(SystemIAsyncResult)
cfc fec SystemNetConnectionReadComplete(Int SystemNetWebExceptionStatus)
可以看到 Page_Load 和 BeginAsyncOperation 方法都是在 ID 為 的線程中被調用其調用源也都是處 理 HTTP 請求的 HttpRuntimeProcessRequest 方法而 EndAsyncOperation 則是在另外一 個 ID 為 的線程中調用調用源也是完成網絡讀操作的 ConnectionReadComplete 方法
而從實現角度來看AddOnPreRenderCompleteAsync 方法將異步頁面處理的啟動和停止方法放到一 個 PagePageAsyncInfo 對象中此對象維護了與頁面相關的各種上下文信息以及開始停止和狀態的數組 而 RegisterAsyncTask 方法也是類似將 PageAsyncTask 實例放到 PageAsyncTaskManager 類型的 管理器中
class Page
{
private PagePageAsyncInfo _asyncInfo;
private PageAsyncTaskManager _asyncTaskManager;
public void AddOnPreRenderCompleteAsync(BeginEventHandler beginHandler EndEventHandler endHandler object state)
{
// 處理參數和狀態異常情況
// 延遲構造異步頁面信息
if (_asyncInfo == null)
_asyncInfo = new PagePageAsyncInfo(this);
_asyncInfoAddHandler(beginHandler endHandler state);
}
public void RegisterAsyncTask(PageAsyncTask task)
{
// 處理參數和狀態異常情況
// 延遲構造異步任務管理器
if (this_asyncTaskManager == null)
_asyncTaskManager = new PageAsyncTaskManager(this);
_asyncTaskManagerAddTask(task);
}
}
HttpApplication 在處理頁面請求 時通過其 pipeline 的 CallHandlerExecutionStep 步驟調用頁面的 BeginProcessRequest 方 法其偽代碼如下
void HttpApplicationIExecutionStepExecute()
{
// 從上下文中獲取獲取當前頁面的處理器
HttpContext context = _applicationContext;
IHttpHandler handler = contextHandler;
if (handler == null)
{
_sync = true;
}
else if (handler is IHttpAsyncHandler)
{
// 如果是異步處理器則調用異步處理開始方法
IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler) handler;
_sync = false;
_handler = asyncHandler;
IAsyncResult result = asyncHandlerBeginProcessRequest(context _completionCallback null);
// 如果的確是異步操作就直接返回
if (!resultCompletedSynchronously)
return;
// 否則恢復同步的頁面處理流程
_sync = true;
_handler = null;
asyncHandlerEndProcessRequest(result);
}
else
{
// 采用同步模式處理頁面
_sync = true;
_applicationSyncContextSetSyncCaller();
try
{
handlerProcessRequest(context);
}
finally
{
_applicationSyncContextResetSyncCaller();
}
}
}
而 ASPNET 頁面一旦通過 Page 標記 定義為異步模式其編譯生成的 Page 子類就會實現 IHttpAsyncHandler 接口
例如對上述例子我們可以通 過 !ClrStack 命令看到頁面被編譯為名稱為 ASPasyncpage_aspx 的類型
!clrstack
OS Thread Id: xb ()
ESP EIP
dd ac AsyncPagePage_Load(SystemObject SystemEventArgs)
ddc be SystemWebUIPageAsyncPageBeginProcessRequest(SystemWebHttpContext SystemAsyncCallback SystemObject)
dd b ASPasyncpage_aspxBeginProcessRequest(SystemWebHttpContext SystemAsyncCallback SystemObject)
ddec bb SystemWebHttpApplication+CallHandlerExecutionStepSystemWebHttpApplicationIExecutionStepExecute()
de c SystemWebHttpApplicationExecuteStep(IExecutionStep Boolean ByRef)
進 一步使用 !DumpDomain 命令可以找到其頁面編譯的臨時文件如
!DumpDomain
Assembly: ade [D:\WINDOWS\MicrosoftNET\Framework\v\Temporary ASPNET Files\asyncpage\baa\cdd\App_Web_ngemvdll]
ClassLoader: abc
SecurityDescriptor: ac
Module Name
acc D:\WINDOWS\MicrosoftNET\Framework\v\Temporary ASPNET Files\asyncpage\baa\cdd\App_Web_ngemvdll
使 用 IL 反匯編根據打開此文件可以看到 AsyncPageaspx 被編譯為 asyncpage_aspx 類型如下所示
public class asyncpage_aspx : AsyncPage IHttpAsyncHandler IHttpHandler
{
public virtual IAsyncResult BeginProcessRequest(HttpContext context AsyncCallback cb object data)
{
return baseAsyncPageBeginProcessRequest(context cb data);
}
public virtual void EndProcessRequest(IAsyncResult ar)
{
baseAsyncPageEndProcessRequest(ar);
}
}
public interface IHttpAsyncHandler : IHttpHandler
{
// Methods
IAsyncResult BeginProcessRequest(HttpContext context AsyncCallback cb object extraData);
void EndProcessRequest(IAsyncResult result);
}
其中 AsyncPage 類型是後台實現代碼編譯 生成的類型asyncpage_aspx 則是 aspx 頁面編譯生成
而 在 PageAsyncPageBeginProcessRequest 方法中將首先處理上下文環境初始化初始化異步執行信息以及相應回調函數 的執行然後會調 用 PageAsyncTaskManagerRegisterHandlersForPagePreRenderCompleteAsync 將異步 任務管理器中所有的異步任務封裝後注冊到 PagePageAsyncInfo 對象中維護的異步調用信息中最後調 用 其 CallHandlers 方法完成對異步處理開始方法的調用完整的偽代碼如下
class Page
{
protected IAsyncResult AsyncPageBeginProcessRequest(HttpContext context AsyncCallback callback object extraData)
{
// 處理上下文環境初始化
// 初始化異步執行信息
_asyncInfoAsyncResult = new HttpAsyncResult(callback extraData);
_asyncInfoCallerIsBlocking = callback == null;
// 執行相應回調函數
// 注冊異步任務
if ((_asyncTaskManager != null) && !_asyncInfoCallerIsBlocking)
_asyncTaskManagerRegisterHandlersForPagePreRenderCompleteAsync();
// 調用所有的異步處理開始方法
_asyncInfoCallHandlers(true);
return _asyncInfoAsyncResult;
}
}
而 在 PageAsyncTaskManager 中被管理的異步任務會作為一個異步執行信息注冊到 PageAsyncInfo 中去並在其被調用 時實際調用 PageAsyncTaskManager 類型的 ExecuteTasks 方法實現較為復雜的異步調用邏輯
internal class PageAsyncTaskManager
{
internal void RegisterHandlersForPagePreRenderCompleteAsync()
{
_pageAddOnPreRenderCompleteAsync(new BeginEventHandler(thisBeginExecuteAsyncTasks) new EndEventHandler(thisEndExecuteAsyncTasks));
}
private IAsyncResult BeginExecuteAsyncTasks(object sender EventArgs e AsyncCallback cb object extraData)
{
return ExecuteTasks(cb extraData);
}
private void EndExecuteAsyncTasks(IAsyncResult ar)
{
_asyncResultEnd();
}
}
以上我們對異步頁面的目的范圍使用方式 和實現原理等有了一個大致的了解並針對異步任務的管理做了簡要的分析基本上已經能弄清異步頁面的靜態運行機制如何下一節我們將從動態執行的角度 對兩級異步任務以及相應的調度和線程使用做進一步探索
From:http://tw.wingwit.com/Article/program/net/201311/13118.html