ASP
NET
提供了大量新功能
其中包括聲明性數據綁定和母版頁
成員和角色管理服務等
但我認為最棒的功能是異步頁
接下來讓我告訴您其中的原因
當 ASP
NET 接收針對頁的請求時
它從線程池中提取一個線程並將請求分配給該線程
一個普通的(或同步的)頁在該請求期間保留線程
從而防止該線程用於處理其他請求
如果一個同步請求成為 I/O 綁定(例如
如果它調用一個遠程 Web 服務或查詢一個遠程數據庫
並等待調用返回)
那麼分配給該請求的線程在調用返回之前處於掛起狀態
這影響了可伸縮性
原因是線程池的可用線程是有限的
如果所有請求處理線程全部阻塞以等待 I/O 操作完成
則其他請求排入隊列等待線程釋放
最好的情況是吞吐量減少
因為請求等待較長的時間才能得到處理
最壞的情況則是該隊列填滿
並且 ASP
NET 因
Server Unavailable
錯誤使後續請求失敗
異步頁為由 I/O 綁定的請求引起的問題提供優秀的解決方案
頁處理從線程池線程開始
但是當一個異步 I/O 操作開始響應 ASP
NET 的信號之後
該線程返回線程池
當該操作完成時
ASP
NET 從線程池提取另一個線程
並完成該請求的處理
由於線程池線程得到了更高效的使用
因此提高了可伸縮性
那些掛起等待 I/O 完成的線程現在可用於服務其他請求
直接的受益方是不執行長時間 I/O 操作並因此可以快速進出管線的請求
長時間等待進入管線會對此類請求的性能帶來不小的負面影響
ASP
NET
Beta
異步頁基礎結構的相關文檔很少
讓我們展望一下異步頁的前景
從而彌補這點不足
請記住
本專欄涉及 ASP
NET
和
NET Framework
的測試版本
ASPNET x 中的異步頁
ASPNET x 本質上不支持異步頁但是通過堅韌的努力和不懈地創新可以生成異步頁有關更多概述信息請參閱相關資料
這裡的技巧是在一個頁的代碼隱藏類中實現 IhttpAsyncHandler從而提示 ASPNET 通過調用 IHttpAsyncHandlerBeginProcessRequest 來處理請求而不是通過調用該頁的 IHttpHandlerProcessRequest 方法然後您的 BeginProcessRequest 實現可以啟動另一個線程該線程調用 baseProcessRequest使得頁進入其常規請求處理生命周期(完成諸如 Load 和 Render 的事件)但是在非 ThreadPool 線程上例外同時啟動新線程之後 BeginProcessRequest 立即返回從而允許執行 BeginProcessRequest 的線程返回線程池
這是基本思想但細節中還有很多注意事項其中您需要實現 IAsyncResult並從 BeginProcessRequest 中返回它這通常意味著創建一個 ManualResetEvent 對象並且當 ProcessRequest 在後台線程中返回時向其發送信號此外您必須提供調用 baseProcessRequest 的線程遺憾的是多數用於將工作移到後台線程的常規技術(包括 ThreadStartThreadPoolQueueUserWorkItem 和異步委托)在 ASPNET 應用程序中都是起反作用的因為它們或者從線程池偷盜線程或者有不受限制的線程增長的危險正確的異步頁實現使用自定義線程池但自定義線程池類不容易編寫
主要是在 ASPNET x 中生成異步頁並非不可能而是有些乏味在嘗試一兩次之後您不禁會想一定會有更好的方法目前這個好方法就是 ASPNET
ASPNET 中的異步頁
ASPNET 極大地簡化了生成異步頁的方式首先使用該頁的 @ Page 指令引入 Async=true 屬性如下所示
在後台這會通知 ASPNET 在該頁中實現 IhttpAsyncHandler接下來您在該頁生存期的早期(例如在 Page_Load 時)調用新的 PageAddOnPreRenderCompleteAsync 方法來注冊一個 Begin 方法和一個 End 方法
如以下代碼所示
AddOnPreRenderCompleteAsync (
new BeginEventHandler(MyBeginMethod)
new EndEventHandler (MyEndMethod)
);
接下來的操作比較有趣該頁經歷其常規處理生命周期直到 PreRender 事件剛剛引發之後然後ASPNET 調用使用 AddOnPreRenderCompleteAsync 注冊的 Begin 方法Begin 方法的任務是啟動諸如數據庫查詢或 Web 服務調用的異步操作並立即返回此時分配給該請求的線程返回到線程池此外Begin 方法返回 IAsyncResult它允許 ASPNET 確定異步操作完成的時間這個時候 ASPNET 從線程池提取線程並調用 End 方法當 End 返回之後ASPNET 執行該頁生命周期其余的部分包括呈現階段在 Begin 返回以及調用 End 之間該請求處理線程可以自由地服務於其他請求直至調用 End 且延遲呈現為止由於 版的 NET Framework 提供多種執行異步操作的方式因此您甚至無需實現 IasyncResult反之Framework 替您實現
圖 中的代碼隱藏類提供一個示例響應頁包含一個 ID 為Output的 Label 控件該頁使用 SystemNetHttpWebRequest 類提取 的內容然後它分析返回的 HTML並將它發現的全部 HREF 目標列表寫出到 Label 控件
圖
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(
);
}
OutputText = builderToString ();
}
}
由於 HTTP 請求需要較長時間才能返回因此AsyncPageaspxcs 異步執行對它的處理它在 Page_Load 中注冊 Begin 和 End 方法並且在 Begin 方法中它調用 HttpWebRequestBeginGetResponse 啟用一個異步 HTTP 請求BeginAsyncOperation 將由 BeginGetResponse 返回的 IAsyncResult 返回到 ASPNET導致當 HTTP 請求完成時ASPNET 調用 EndAsyncOperationEndAsyncOperation 進而分析該內容並將結果寫入 Label 控件之後進行呈現並且 HTTP 響應返回到浏覽器
圖 同步和異步頁處理
圖 說明 ASPNET 同步和異步頁之間的區別當請求同步頁時ASPNET 為該請求分配線程池中的一個線程並在該線程上執行頁如果該請求停止執行 I/O 操作則掛起線程直到完成操作從而可以完成該頁的生命周期相反異步頁通常通過 PreRender 事件執行然後調用使用 AddOnPreRenderCompleteAsync 注冊的 Begin 方法之後該請求處理線程返回線程池Begin 啟動一個異步 I/O 操作當該操作完成時ASPNET 從線程池提取另一個線程並調用 End 方法並且在該線程上執行該頁生命周期的其余部分
圖 跟蹤輸出顯示異步頁的異步點
對 Begin 的調用標記該頁的異步點圖 中的跟蹤准確顯示異步點發生在何處如果調用則必須在異步點之前調用 AddOnPreRenderCompleteAsync — 即不晚於該頁的 PreRender 事件
異步數據綁定
通常情況下ASPNET 頁並不使用 HttpWebRequest 直接請求其他頁但它們通常查詢數據庫並對結果進行數據綁定因此您將如何使用異步頁執行異步數據綁定呢?圖 中的代碼隱藏類顯示進行此操作的一種方式
using System;
using SystemData;
using SystemDataSqlClient;
using SystemWeb;
using SystemWebUI;
using SystemWebUIWebControls;
using SystemWebConfiguration;
public partial class AsyncDataBind : SystemWebUIPage
{
private SqlConnection _connection;
private SqlCommand _command;
private SqlDataReader _reader;
protected void Page_Load(object sender EventArgs e)
{
if (!IsPostBack)
{
// Hook PreRenderComplete event for data binding
thisPreRenderComplete += new EventHandler(Page_PreRenderComplete);
// Register async methods
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation)
new EndEventHandler(EndAsyncOperation)
);
}
}
IAsyncResult BeginAsyncOperation (object sender EventArgs e AsyncCallback cb object state)
{
string connect = WebConfigurationManagerConnectionStrings
[PubsConnectionString]ConnectionString;
_connection = new SqlConnection(connect);
_connectionOpen();
_command = new SqlCommand(SELECT title_id title price FROM titles _connection);
return _commandBeginExecuteReader (cb state);
}
void EndAsyncOperation(IAsyncResult ar)
{
_reader = _commandEndExecuteReader(ar);
}
protected void Page_PreRenderComplete(object sender EventArgs e)
{
OutputDataSource = _reader;
OutputDataBind();
}
public override void Dispose()
{
if (_connection != null) _connectionClose();
baseDispose();
}
}
AsyncDataBindaspxcs 與 AsyncPageaspxcs 使用相同的 AddOnPreRenderCompleteAsync 模式但是AsyncDataBindaspxcs 的 BeginAsyncOperation 方法調用 ADONET 中的新方法 SqlCommandBeginExecuteReader(而非 HttpWebRequestBeginGetResponse)以執行一個異步數據庫查詢當調用完成時EndAsyncOperation 調用 SqlCommandEndExecuteReader 以獲取 SqlDataReader然後將其存儲在私有字段中在用於 PreRenderComplete 事件(在異步操作完成但呈現該頁之前引發)的事件處理程序中AsyncDataBindaspxcs 之後將 SqlDataReader 綁定到 Output GridView 控件從外觀上看該頁類似於使用 GridView 呈現數據庫查詢結果的普通(同步)頁但是在內部該頁更具可伸縮性因為它並不掛起線程池線程以等待查詢返回
異步調用 Web 服務
另一個通常由 ASPNET Web 頁執行的
與 I/O 相關的任務是調出 Web 服務由於 Web 服務調用花費較長時間才能返回因此執行它們的頁是用於異步處理的理想選擇
圖
using System;
using SystemData;
using SystemConfiguration;
using SystemWeb;
using SystemWebUI;
using SystemWebUIWebControls;
public partial class AsyncWSInvoke : SystemWebUIPage
{
private WSPubsWebService _ws;
private DataSet _ds;
protected void Page_Load(object sender EventArgs e)
{
if (!IsPostBack)
{
// Hook PreRenderComplete event for data binding
thisPreRenderComplete += new EventHandler(Page_PreRenderComplete);
// Register async methods
AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsyncOperation)
new EndEventHandler(EndAsyncOperation)
);
}
}
IAsyncResult BeginAsyncOperation (object sender EventArgs e AsyncCallback cb object state)
{
_ws = new WSPubsWebService();
// Fix up URL for call to local VWDhosted Web service
_wsUrl = new Uri(RequestUrl Pubsasmx)ToString();
_wsUseDefaultCredentials = true;
return _wsBeginGetTitles (cb state);
}
void EndAsyncOperation(IAsyncResult ar)
{
_ds = _wsEndGetTitles(ar);
}
protected void Page_PreRenderComplete(object sender EventArgs e)
{
OutputDataSource = _ds;
OutputDataBind();
}
public override void Dispose()
{
if (_ws != null) _wsDispose();
baseDispose();
}
}
圖 顯示生成調出 Web 服務的異步頁的方式它使用圖 和 圖 中相同的 AddOnPreRenderCompleteAsync 機制該頁的 Begin 方法通過調用 Web 服務代理的異步 Begin 方法啟動一個異步 Web 服務調用該頁的 End 方法在私有字段中緩存對 Web 方法返回的 DataSet 的引用並且 PreRenderComplete 處理程序將 DataSet 綁定到 GridView作為參考該調用的目標 Web 方法如以下代碼所示
[WebMethod]
public DataSet GetTitles ()
{
string connect = WebConfigurationManagerConnectionStrings
[PubsConnectionString]ConnectionString;
SqlDataAdapter adapter = new SqlDataAdapter
(SELECT title_id title price FROM titles connect);
DataSet ds = new DataSet();
adapterFill(ds);
return ds;
}
這只是其中一種方式
但並不是唯一的方式
NET Framework
Web 服務代理支持兩種對 Web 服務進行異步調用的機制
一個是
NET Framework
x 和
Web 服務代理中的每方法 Begin 和 End 方法
另一個是僅由
NET Framework
的 Web 服務代理提供的新 MethodAsync 方法和 MethodCompleted 事件
如果一個 Web 服務有一個名為 Foo 的方法
那麼除了具有名為 Foo
BeginFoo 和 EndFoo 的方法外
NET Framework 版本
Web 服務代理還包括名為 FooAsync 的方法和名為 FooCompleted 的事件
可以通過注冊 FooCompleted 事件的處理程序並調用 FooAsync 來異步調用 Foo
如下所示
proxyFooCompleted += new FooCompletedEventHandler (OnFooCompleted);
proxyFooAsync ();
void OnFooCompleted (Object source FooCompletedEventArgs e)
{
// Called when Foo completes
}
當異步調用由於 FooAsync 完成而開始時
將引發 FooCompleted 事件
從而導致調用 FooCompleted 事件處理程序
包裝該事件處理程序 (FooCompletedEventHandler) 的委托和傳遞給它的第二個參數 (FooCompletedEventArgs) 都隨 Web 服務代理一起生成
可通過 FooCompletedEventArgs
Result 訪問 Foo 的返回值
圖
展示使用 MethodAsync 模式異步調用 Web 服務的 GetTitles 方法的代碼隱藏類
從功能上講
該頁等同於圖
中的頁
但其內部實現則大為不同
AsyncWSInvoke
aspx 包括一個 @ Page Async=
true
指令
類似於 AsyncWSInvoke
aspx
但是
AsyncWSInvoke
aspx
cs 並不調用 AddOnPreRenderCompleteAsync
它注冊一個用於 GetTitlesCompleted 事件的處理程序
並調用 Web 服務代理上的 GetTitlesAsync
ASP
NET 仍然延遲呈現該頁
直到 GetTitlesAsync 完成
在內部
當異步調用開始以及完成時
它使用 System
Threading
SynchronizationContext 的一個實例(
的一個新類)接收通知
圖
using System;
using System
Data;
using System
Configuration;
using System
Web;
using System
Web
UI;
using System
Web
UI
WebControls;
public partial class AsyncWSInvoke : SystemWebUIPage
{
private WSPubsWebService _ws;
private DataSet _ds;
protected void Page_Load(object sender EventArgs e)
{
if (!IsPostBack)
{
// Hook PreRenderComplete event for data binding
thisPreRenderComplete += new EventHandler(Page_PreRenderComplete);
// Call the Web service asynchronously
_ws = new WSPubsWebService();
_wsGetTitlesCompleted += new
WSGetTitlesCompletedEventHandler(GetTitlesCompleted);
_wsUrl = new Uri(RequestUrl Pubsasmx)ToString();
_wsUseDefaultCredentials = true;
_wsGetTitlesAsync();
}
}
void GetTitlesCompleted(Object source
WSGetTitlesCompletedEventArgs e)
{
_ds = eResult;
}
protected void Page_PreRenderComplete(object sender EventArgs e)
{
OutputDataSource = _ds;
OutputDataBind();
}
public override void Dispose()
{
if (_ws != null) _wsDispose();
baseDispose();
}
}
使用 MethodAsync 而非 AddOnPreRenderCompleteAsync 實現異步頁有兩個優勢
首先
MethodAsync 將模擬
區域性和 HttpContext
Current 注入 MethodCompleted 事件處理程序
而 AddOnPreRenderCompleteAsync 則不然
其次
如果該頁進行多個異步調用
而且必須延遲呈現直到所有調用完成
則使用 AddOnPreRenderCompleteAsync 要求您生成一個在所有調用完成前保持無信號狀態的 IasyncResult
使用 MethodAsync
這樣的操作就不是必需的
您只需放置這些調用(數量不限)
ASP
NET 引擎延遲該呈現階段
直到最後一個調用返回
異步任務
MethodAsync 是從異步頁進行多個異步 Web 服務調用並延遲呈現階段直到所有調用完成的一個簡便方法但如果您想在一個異步頁中執行若干異步 I/O 操作而且這些操作不涉及 Web 服務那該如何呢? 這麼說可以反過來生成一個 IAsyncResult它可以返回到 ASPNET 以允許它了解最後一個調用何時完成的嗎? 幸運的是答案是否定的
在 ASPNET 中SystemWebUIPage 類引入了另一個方法來簡化異步操作 RegisterAsyncTaskRegisterAsyncTask 比 AddOnPreRenderCompleteAsync 具有四個優勢首先除了 Begin 和 End 方法RegisterAsyncTask 還允許您注冊當異步操作長時間無法完成時調用的超時方法您可以通過在該頁的 @ Page 指令中包含 AsyncTimeout 屬性以聲明性方式設置超時AsyncTimeout= 將超時設置為 秒第二個優勢是您可以在一個請求中多次調用 RegisterAsyncTask 來注冊若干異步操作和使用 MethodAsync 一樣ASPNET 延遲呈現該頁直到所有操作完成第三您可以使用 RegisterAsyncTask 的第四個參數將狀態傳遞給 Begin 方法最後RegisterAsyncTask 將模擬區域性和 HttpContextCurrent 注入 End 和 Timeout 方法正如本文前面提到的使用 AddOnPreRenderCompleteAsync 注冊的 End 方法的情況則不然
在其他方面依賴於 RegisterAsyncTask 的異步頁與依賴於 AddOnPreRenderCompleteAsync 的異步頁相類似它仍然需要 @ Page 指令(或等效的編程指令它會將該頁的 AsyncMode 屬性設置為 true)中的 Async=true 屬性而且它仍然與平時一樣通過 PreRender 事件執行此時調用使用 RegisterAsyncTask 注冊的 Begin 方法而且進一步保持請求處理直到最後一個操作完成
例如圖 中的代碼隱藏類在功能上與圖 中的等效但是它使用 RegisterTaskAsync 而非使用 AddOnPreRenderCompleteAsync請注意名為 TimeoutAsyncOperation 的超時處理程序如果 HttpWebRequestBeginGetRequest 長時間無法完成將調用該處理程序相應的 aspx 文件包括一個將超時間隔設置為 秒的 AsyncTimeout 屬性還請注意傳給 RegisterAsyncTask 的第四個參數(可用於將數據傳送到 Begin 方法)的 null
圖
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(
);
}
OutputText = builderToString();
}
void TimeoutAsyncOperation(IAsyncResult ar)
{
OutputText = Data temporarily unavailable;
}
}
RegisterAsyncTask 的主要優勢在於它允許異步頁引發多個異步調用並延遲呈現直到所有調用完成它也很好地適用於單個異步調用而且它提供了 AddOnPreRenderCompleteAsync 不具有的超時選項如果生成一個只進行一個異步調用的異步頁您可以使用 AddOnPreRenderCompleteAsync 或 RegisterAsyncTask但對於放置兩個以上異步調用的異步頁RegisterAsyncTask 極大地簡化了您的操作
由於超時值是每頁而非每調用設置因此您可能想知道是否能改變單個調用的超時值簡單的回答是否您可以通過以編程方式修改頁的 AsyncTimeout 屬性逐個請求地更改超時但是您無法將不同超時分配給從同一請求初始化的不同調用
包裝它
現在您已經了解了 ASPNET 中異步頁的實質它們在即將推出的 ASPNET 版本中非常易於實現並且其體系結構允許您在一個請求中批處理多個異步 I/O 操作並延遲該頁的呈現直到所有操作完成通過與異步 ADONET 和 NET Framework 中的其他新異步功能相結合異步 ASPNET 頁針對因充滿線程池而限制可伸縮性的 I/O 綁定請求問題提供了解決方案
當生成異步頁時最後需要注意的一點是不應該啟動來自 ASPNET 使用的同一線程池的異步操作例如在頁的異步點調用 ThreadPoolQueueUserWorkItem 會起反作用因為該方法來自線程池從而導致純粹獲取用於處理請求的零線程相反調用內置於 Framework 中的異步方法(例如方法 HttpWebRequestBeginGetResponse 和 SqlCommandBeginExecuteReader)通常認為是安全的因為這些方法傾向於使用完成端口實現異步行為
From:http://tw.wingwit.com/Article/program/net/201311/11499.html