ASP.NET 2.0 提供了大量新功能,其中包括聲明性數(shù)據(jù)綁定和母版頁(yè),成員和角色管理服務(wù)等。但我認(rèn)為最棒的功能是異步頁(yè),接下來(lái)讓我告訴您其中的原因。
當(dāng) ASP.NET 接收針對(duì)頁(yè)的請(qǐng)求時(shí),它從線(xiàn)程池中提取一個(gè)線(xiàn)程并將請(qǐng)求分配給該線(xiàn)程。一個(gè)普通的(或同步的)頁(yè)在該請(qǐng)求期間保留線(xiàn)程,從而防止該線(xiàn)程用于處理其他請(qǐng)求。如果一個(gè)同步請(qǐng)求成為 I/O 綁定(例如,如果它調(diào)用一個(gè)遠(yuǎn)程 Web 服務(wù)或查詢(xún)一個(gè)遠(yuǎn)程數(shù)據(jù)庫(kù),并等待調(diào)用返回),那么分配給該請(qǐng)求的線(xiàn)程在調(diào)用返回之前處于掛起狀態(tài)。這影響了可伸縮性,原因是線(xiàn)程池的可用線(xiàn)程是有限的。如果所有請(qǐng)求處理線(xiàn)程全部阻塞以等待 I/O 操作完成,則其他請(qǐng)求排入隊(duì)列等待線(xiàn)程釋放。最好的情況是吞吐量減少,因?yàn)檎?qǐng)求等待較長(zhǎng)的時(shí)間才能得到處理。最壞的情況則是該隊(duì)列填滿(mǎn),并且 ASP.NET 因 503“Server Unavailable”錯(cuò)誤使后續(xù)請(qǐng)求失敗。
異步頁(yè)為由 I/O 綁定的請(qǐng)求引起的問(wèn)題提供優(yōu)秀的解決方案。頁(yè)處理從線(xiàn)程池線(xiàn)程開(kāi)始,但是當(dāng)一個(gè)異步 I/O 操作開(kāi)始響應(yīng) ASP.NET 的信號(hào)之后,該線(xiàn)程返回線(xiàn)程池。當(dāng)該操作完成時(shí),ASP.NET 從線(xiàn)程池提取另一個(gè)線(xiàn)程,并完成該請(qǐng)求的處理。由于線(xiàn)程池線(xiàn)程得到了更高效的使用,因此提高了可伸縮性。那些掛起等待 I/O 完成的線(xiàn)程現(xiàn)在可用于服務(wù)其他請(qǐng)求。直接的受益方是不執(zhí)行長(zhǎng)時(shí)間 I/O 操作并因此可以快速進(jìn)出管線(xiàn)的請(qǐng)求。長(zhǎng)時(shí)間等待進(jìn)入管線(xiàn)會(huì)對(duì)此類(lèi)請(qǐng)求的性能帶來(lái)不小的負(fù)面影響。
ASP.NET 2.0 Beta 2 異步頁(yè)基礎(chǔ)結(jié)構(gòu)的相關(guān)文檔很少。讓我們展望一下異步頁(yè)的前景,從而彌補(bǔ)這點(diǎn)不足。請(qǐng)記住,本專(zhuān)欄涉及 ASP.NET 2.0 和 .NET Framework 2.0 的測(cè)試版本。。
ASP.NET 1.x 中的異步頁(yè) ASP.NET 1.
x 本質(zhì)上不支持異步頁(yè),但是通過(guò)堅(jiān)韌的努力和不懈地創(chuàng)新可以生成異步頁(yè)。有關(guān)更多概述信息,請(qǐng)參閱相關(guān)資料
這里的技巧是,在一個(gè)頁(yè)的代碼隱藏類(lèi)中實(shí)現(xiàn) IhttpAsyncHandler,從而提示 ASP.NET 通過(guò)調(diào)用 IHttpAsyncHandler.BeginProcessRequest 來(lái)處理請(qǐng)求,而不是通過(guò)調(diào)用該頁(yè)的 IHttpHandler.ProcessRequest 方法。然后,您的 BeginProcessRequest 實(shí)現(xiàn)可以啟動(dòng)另一個(gè)線(xiàn)程。該線(xiàn)程調(diào)用 base.ProcessRequest,使得頁(yè)進(jìn)入其常規(guī)請(qǐng)求處理生命周期(完成諸如 Load 和 Render 的事件),但是在非 ThreadPool 線(xiàn)程上例外。同時(shí),啟動(dòng)新線(xiàn)程之后 BeginProcessRequest 立即返回,從而允許執(zhí)行 BeginProcessRequest 的線(xiàn)程返回線(xiàn)程池。
這是基本思想,但細(xì)節(jié)中還有很多注意事項(xiàng)。其中,您需要實(shí)現(xiàn) IAsyncResult,并從 BeginProcessRequest 中返回它。這通常意味著創(chuàng)建一個(gè) ManualResetEvent 對(duì)象,并且當(dāng) ProcessRequest 在后臺(tái)線(xiàn)程中返回時(shí)向其發(fā)送信號(hào)。此外,您必須提供調(diào)用 base.ProcessRequest 的線(xiàn)程。遺憾的是,多數(shù)用于將工作移到后臺(tái)線(xiàn)程的常規(guī)技術(shù)(包括 Thread.Start、ThreadPool.QueueUserWorkItem 和異步委托)在 ASP.NET 應(yīng)用程序中都是起反作用的,因?yàn)樗鼈兓蛘邚木€(xiàn)程池“偷盜”線(xiàn)程,或者有不受限制的線(xiàn)程增長(zhǎng)的危險(xiǎn)。正確的異步頁(yè)實(shí)現(xiàn)使用自定義線(xiàn)程池,但自定義線(xiàn)程池類(lèi)不容易編寫(xiě)。
主要是在 ASP.NET 1.
x 中生成異步頁(yè)并非不可能,而是有些乏味。在嘗試一、兩次之后,您不禁會(huì)想一定會(huì)有更好的方法。目前,這個(gè)好方法就是 ASP.NET 2.0。
ASP.NET 2.0 中的異步頁(yè) ASP.NET 2.0 極大地簡(jiǎn)化了生成異步頁(yè)的方式。首先使用該頁(yè)的 @ Page 指令引入 Async=“true” 屬性,如下所示:
在后臺(tái),這會(huì)通知 ASP.NET 在該頁(yè)中實(shí)現(xiàn) IhttpAsyncHandler。接下來(lái),您在該頁(yè)生存期的早期(例如,在 Page_Load 時(shí))調(diào)用新的 Page.AddOnPreRenderCompleteAsync 方法來(lái)注冊(cè)一個(gè) Begin 方法和一個(gè) End 方法,如以下代碼所示:
AddOnPreRenderCompleteAsync (
new BeginEventHandler(MyBeginMethod),
new EndEventHandler (MyEndMethod)
);
|
接下來(lái)的操作比較有趣。該頁(yè)經(jīng)歷其常規(guī)處理生命周期,直到 PreRender 事件剛剛引發(fā)之后。然后,ASP.NET 調(diào)用使用 AddOnPreRenderCompleteAsync 注冊(cè)的 Begin 方法。Begin 方法的任務(wù)是啟動(dòng)諸如數(shù)據(jù)庫(kù)查詢(xún)或 Web 服務(wù)調(diào)用的異步操作,并立即返回。此時(shí),分配給該請(qǐng)求的線(xiàn)程返回到線(xiàn)程池。此外,Begin 方法返回 IAsyncResult,它允許 ASP.NET 確定異步操作完成的時(shí)間,這個(gè)時(shí)候 ASP.NET 從線(xiàn)程池提取線(xiàn)程并調(diào)用 End 方法。當(dāng) End 返回之后,ASP.NET 執(zhí)行該頁(yè)生命周期其余的部分,包括呈現(xiàn)階段。在 Begin 返回以及調(diào)用 End 之間,該請(qǐng)求處理線(xiàn)程可以自由地服務(wù)于其他請(qǐng)求,直至調(diào)用 End 且延遲呈現(xiàn)為止。由于 2.0 版的 .NET Framework 提供多種執(zhí)行異步操作的方式,因此,您甚至無(wú)需實(shí)現(xiàn) IasyncResult。反之,F(xiàn)ramework 替您實(shí)現(xiàn)。
圖 1 中的代碼隱藏類(lèi)提供一個(gè)示例。響應(yīng)頁(yè)包含一個(gè) ID 為“Output”的 Label 控件。該頁(yè)使用 System.Net.HttpWebRequest 類(lèi)提取 http://MSDN.microsoft.com 的內(nèi)容。然后,它分析返回的 HTML,并將它發(fā)現(xiàn)的全部 HREF 目標(biāo)列表寫(xiě)出到 Label 控件。
圖1
using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Net; using System.IO; using System.Text; using System.Text.RegularExpressions;
public partial class AsyncPage : System.Web.UI.Page { 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 = WebRequest.Create("http://msdn.microsoft.com"); return _request.BeginGetResponse (cb, state); } void EndAsyncOperation (IAsyncResult ar) { string text; using (WebResponse response = _request.EndGetResponse(ar)) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { text = reader.ReadToEnd(); } }
Regex regex = new Regex ("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase); MatchCollection matches = regex.Matches(text);
StringBuilder builder = new StringBuilder(1024); foreach (Match match in matches) { builder.Append (match.Groups[1]); builder.Append("<br/>"); }
Output.Text = builder.ToString (); } }
|
由于 HTTP 請(qǐng)求需要較長(zhǎng)時(shí)間才能返回,因此,AsyncPage.aspx.cs 異步執(zhí)行對(duì)它的處理。它在 Page_Load 中注冊(cè) Begin 和 End 方法,并且在 Begin 方法中,它調(diào)用 HttpWebRequest.BeginGetResponse 啟用一個(gè)異步 HTTP 請(qǐng)求。BeginAsyncOperation 將由 BeginGetResponse 返回的 IAsyncResult 返回到 ASP.NET,導(dǎo)致當(dāng) HTTP 請(qǐng)求完成時(shí),ASP.NET 調(diào)用 EndAsyncOperation。EndAsyncOperation 進(jìn)而分析該內(nèi)容并將結(jié)果寫(xiě)入 Label 控件,之后進(jìn)行呈現(xiàn),并且 HTTP 響應(yīng)返回到瀏覽器。
圖 2 同步和異步頁(yè)處理
圖 2 說(shuō)明 ASP.NET 2.0 同步和異步頁(yè)之間的區(qū)別。當(dāng)請(qǐng)求同步頁(yè)時(shí),ASP.NET 為該請(qǐng)求分配線(xiàn)程池中的一個(gè)線(xiàn)程,并在該線(xiàn)程上執(zhí)行頁(yè)。如果該請(qǐng)求停止執(zhí)行 I/O 操作,則掛起線(xiàn)程,直到完成操作,從而可以完成該頁(yè)的生命周期。相反,異步頁(yè)通常通過(guò) PreRender 事件執(zhí)行。然后,調(diào)用使用 AddOnPreRenderCompleteAsync 注冊(cè)的 Begin 方法,之后,該請(qǐng)求處理線(xiàn)程返回線(xiàn)程池。Begin 啟動(dòng)一個(gè)異步 I/O 操作,當(dāng)該操作完成時(shí),ASP.NET 從線(xiàn)程池提取另一個(gè)線(xiàn)程并調(diào)用 End 方法,并且在該線(xiàn)程上執(zhí)行該頁(yè)生命周期的其余部分。

圖 3 跟蹤輸出顯示異步頁(yè)的異步點(diǎn)
對(duì) Begin 的調(diào)用標(biāo)記該頁(yè)的“異步點(diǎn)”。圖 3 中的跟蹤準(zhǔn)確顯示異步點(diǎn)發(fā)生在何處。如果調(diào)用,則必須在異步點(diǎn)之前調(diào)用 AddOnPreRenderCompleteAsync — 即,不晚于該頁(yè)的 PreRender 事件。
異步數(shù)據(jù)綁定
通常情況下,ASP.NET 頁(yè)并不使用 HttpWebRequest 直接請(qǐng)求其他頁(yè),但它們通常查詢(xún)數(shù)據(jù)庫(kù)并對(duì)結(jié)果進(jìn)行數(shù)據(jù)綁定。因此,您將如何使用異步頁(yè)執(zhí)行異步數(shù)據(jù)綁定呢?圖 4 中的代碼隱藏類(lèi)顯示進(jìn)行此操作的一種方式。
using System; using System.Data; using System.Data.SqlClient; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.Configuration;
public partial class AsyncDataBind : System.Web.UI.Page { 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 this.PreRenderComplete += 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 = WebConfigurationManager.ConnectionStrings ["PubsConnectionString"].ConnectionString; _connection = new SqlConnection(connect); _connection.Open(); _command = new SqlCommand("SELECT title_id, title, price FROM titles", _connection); return _command.BeginExecuteReader (cb, state); }
void EndAsyncOperation(IAsyncResult ar) { _reader = _command.EndExecuteReader(ar); }
protected void Page_PreRenderComplete(object sender, EventArgs e) { Output.DataSource = _reader; Output.DataBind(); }
public override void Dispose() { if (_connection != null) _connection.Close(); base.Dispose(); } }
|
AsyncDataBind.aspx.cs 與 AsyncPage.aspx.cs 使用相同的 AddOnPreRenderCompleteAsync 模式。但是,AsyncDataBind.aspx.cs 的 BeginAsyncOperation 方法調(diào)用 ADO.NET 2.0 中的新方法 SqlCommand.BeginExecuteReader(而非 HttpWebRequest.BeginGetResponse),以執(zhí)行一個(gè)異步數(shù)據(jù)庫(kù)查詢(xún)。當(dāng)調(diào)用完成時(shí),EndAsyncOperation 調(diào)用 SqlCommand.EndExecuteReader 以獲取 SqlDataReader,然后將其存儲(chǔ)在私有字段中。在用于 PreRenderComplete 事件(在異步操作完成但呈現(xiàn)該頁(yè)之前引發(fā))的事件處理程序中,AsyncDataBind.aspx.cs 之后將 SqlDataReader 綁定到 Output GridView 控件。從外觀上看,該頁(yè)類(lèi)似于使用 GridView 呈現(xiàn)數(shù)據(jù)庫(kù)查詢(xún)結(jié)果的普通(同步)頁(yè)。但是在內(nèi)部,該頁(yè)更具可伸縮性,因?yàn)樗⒉粧炱鹁€(xiàn)程池線(xiàn)程以等待查詢(xún)返回。
異步調(diào)用 Web 服務(wù)
另一個(gè)通常由 ASP.NET Web 頁(yè)執(zhí)行的、與 I/O 相關(guān)的任務(wù)是調(diào)出 Web 服務(wù)。由于 Web 服務(wù)調(diào)用花費(fèi)較長(zhǎng)時(shí)間才能返回,因此,執(zhí)行它們的頁(yè)是用于異步處理的理想選擇。
圖5
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.UI; using System.Web.UI.WebControls;
public partial class AsyncWSInvoke1 : System.Web.UI.Page { private WS.PubsWebService _ws; private DataSet _ds;
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // Hook PreRenderComplete event for data binding this.PreRenderComplete += 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 WS.PubsWebService(); // Fix up URL for call to local VWD-hosted Web service _ws.Url = new Uri(Request.Url, "Pubs.asmx").ToString(); _ws.UseDefaultCredentials = true; return _ws.BeginGetTitles (cb, state); }
void EndAsyncOperation(IAsyncResult ar) { _ds = _ws.EndGetTitles(ar); }
protected void Page_PreRenderComplete(object sender, EventArgs e) { Output.DataSource = _ds; Output.DataBind(); }
public override void Dispose() { if (_ws != null) _ws.Dispose(); base.Dispose(); } }
|
圖 5 顯示生成調(diào)出 Web 服務(wù)的異步頁(yè)的方式。它使用圖 1 和 圖 4 中相同的 AddOnPreRenderCompleteAsync 機(jī)制。該頁(yè)的 Begin 方法通過(guò)調(diào)用 Web 服務(wù)代理的異步 Begin 方法啟動(dòng)一個(gè)異步 Web 服務(wù)調(diào)用。該頁(yè)的 End 方法在私有字段中緩存對(duì) Web 方法返回的 DataSet 的引用,并且 PreRenderComplete 處理程序?qū)?DataSet 綁定到 GridView。作為參考,該調(diào)用的目標(biāo) Web 方法如以下代碼所示:
|
[WebMethod]
public DataSet GetTitles ()
{
string connect = WebConfigurationManager.ConnectionStrings
["PubsConnectionString"].ConnectionString;
SqlDataAdapter adapter = new SqlDataAdapter
("SELECT title_id, title, price FROM titles", connect);
DataSet ds = new DataSet();
adapter.Fill(ds);
return ds;
}
|
這只是其中一種方式,但并不是唯一的方式。.NET Framework 2.0 Web 服務(wù)代理支持兩種對(duì) Web 服務(wù)進(jìn)行異步調(diào)用的機(jī)制。一個(gè)是 .NET Framework 1.
x 和 2.0 Web 服務(wù)代理中的每方法 Begin 和 End 方法。另一個(gè)是僅由 .NET Framework 2.0 的 Web 服務(wù)代理提供的新 MethodAsync 方法和 MethodCompleted 事件。
如果一個(gè) Web 服務(wù)有一個(gè)名為 Foo 的方法,那么除了具有名為 Foo、BeginFoo 和 EndFoo 的方法外,.NET Framework 版本 2.0 Web 服務(wù)代理還包括名為 FooAsync 的方法和名為 FooCompleted 的事件。可以通過(guò)注冊(cè) FooCompleted 事件的處理程序并調(diào)用 FooAsync 來(lái)異步調(diào)用 Foo,如下所示:
proxy.FooCompleted += new FooCompletedEventHandler (OnFooCompleted);
proxy.FooAsync (...);
...
void OnFooCompleted (Object source, FooCompletedEventArgs e)
{
// Called when Foo completes
}
|
當(dāng)異步調(diào)用由于 FooAsync 完成而開(kāi)始時(shí),將引發(fā) FooCompleted 事件,從而導(dǎo)致調(diào)用 FooCompleted 事件處理程序。包裝該事件處理程序 (FooCompletedEventHandler) 的委托和傳遞給它的第二個(gè)參數(shù) (FooCompletedEventArgs) 都隨 Web 服務(wù)代理一起生成。可通過(guò) FooCompletedEventArgs.Result 訪(fǎng)問(wèn) Foo 的返回值。
圖 6 展示使用 MethodAsync 模式異步調(diào)用 Web 服務(wù)的 GetTitles 方法的代碼隱藏類(lèi)。從功能上講,該頁(yè)等同于圖 5 中的頁(yè)。但其內(nèi)部實(shí)現(xiàn)則大為不同。AsyncWSInvoke2.aspx 包括一個(gè) @ Page Async=“true” 指令,類(lèi)似于 AsyncWSInvoke1.aspx。但是,AsyncWSInvoke2.aspx.cs 并不調(diào)用 AddOnPreRenderCompleteAsync;它注冊(cè)一個(gè)用于 GetTitlesCompleted 事件的處理程序,并調(diào)用 Web 服務(wù)代理上的 GetTitlesAsync。ASP.NET 仍然延遲呈現(xiàn)該頁(yè),直到 GetTitlesAsync 完成。在內(nèi)部,當(dāng)異步調(diào)用開(kāi)始以及完成時(shí),它使用 System.Threading.SynchronizationContext 的一個(gè)實(shí)例(2.0 的一個(gè)新類(lèi))接收通知。
圖6
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.UI; using System.Web.UI.WebControls;
public partial class AsyncWSInvoke2 : System.Web.UI.Page { private WS.PubsWebService _ws; private DataSet _ds;
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // Hook PreRenderComplete event for data binding this.PreRenderComplete += new EventHandler(Page_PreRenderComplete);
// Call the Web service asynchronously _ws = new WS.PubsWebService(); _ws.GetTitlesCompleted += new WS.GetTitlesCompletedEventHandler(GetTitlesCompleted); _ws.Url = new Uri(Request.Url, "Pubs.asmx").ToString(); _ws.UseDefaultCredentials = true; _ws.GetTitlesAsync(); } }
void GetTitlesCompleted(Object source, WS.GetTitlesCompletedEventArgs e) { _ds = e.Result; }
protected void Page_PreRenderComplete(object sender, EventArgs e) { Output.DataSource = _ds; Output.DataBind(); }
public override void Dispose() { if (_ws != null) _ws.Dispose(); base.Dispose(); } }
|
使用 MethodAsync 而非 AddOnPreRenderCompleteAsync 實(shí)現(xiàn)異步頁(yè)有兩個(gè)優(yōu)勢(shì)。首先,MethodAsync 將模擬、區(qū)域性和 HttpContext.Current 注入 MethodCompleted 事件處理程序,而 AddOnPreRenderCompleteAsync 則不然。其次,如果該頁(yè)進(jìn)行多個(gè)異步調(diào)用,而且必須延遲呈現(xiàn)直到所有調(diào)用完成,則使用 AddOnPreRenderCompleteAsync 要求您生成一個(gè)在所有調(diào)用完成前保持無(wú)信號(hào)狀態(tài)的 IasyncResult。使用 MethodAsync,這樣的操作就不是必需的;您只需放置這些調(diào)用(數(shù)量不限),ASP.NET 引擎延遲該呈現(xiàn)階段,直到最后一個(gè)調(diào)用返回。
異步任務(wù) MethodAsync 是從異步頁(yè)進(jìn)行多個(gè)異步 Web 服務(wù)調(diào)用并延遲呈現(xiàn)階段直到所有調(diào)用完成的一個(gè)簡(jiǎn)便方法。但如果您想在一個(gè)異步頁(yè)中執(zhí)行若干異步 I/O 操作,而且這些操作不涉及 Web 服務(wù),那該如何呢? 這么說(shuō),可以反過(guò)來(lái)生成一個(gè) IAsyncResult,它可以返回到 ASP.NET 以允許它了解最后一個(gè)調(diào)用何時(shí)完成的嗎? 幸運(yùn)的是,答案是否定的。
在 ASP.NET 2.0 中,System.Web.UI.Page 類(lèi)引入了另一個(gè)方法來(lái)簡(jiǎn)化異步操作: RegisterAsyncTask。RegisterAsyncTask 比 AddOnPreRenderCompleteAsync 具有四個(gè)優(yōu)勢(shì)。首先,除了 Begin 和 End 方法,RegisterAsyncTask 還允許您注冊(cè)當(dāng)異步操作長(zhǎng)時(shí)間無(wú)法完成時(shí)調(diào)用的超時(shí)方法。您可以通過(guò)在該頁(yè)的 @ Page 指令中包含 AsyncTimeout 屬性以聲明性方式設(shè)置超時(shí)。AsyncTimeout="5" 將超時(shí)設(shè)置為 5 秒。第二個(gè)優(yōu)勢(shì)是,您可以在一個(gè)請(qǐng)求中多次調(diào)用 RegisterAsyncTask 來(lái)注冊(cè)若干異步操作。和使用 MethodAsync 一樣,ASP.NET 延遲呈現(xiàn)該頁(yè),直到所有操作完成。第三,您可以使用 RegisterAsyncTask 的第四個(gè)參數(shù)將狀態(tài)傳遞給 Begin 方法。最后,RegisterAsyncTask 將模擬、區(qū)域性和 HttpContext.Current 注入 End 和 Timeout 方法。正如本文前面提到的,使用 AddOnPreRenderCompleteAsync 注冊(cè)的 End 方法的情況則不然。
在其他方面,依賴(lài)于 RegisterAsyncTask 的異步頁(yè)與依賴(lài)于 AddOnPreRenderCompleteAsync 的異步頁(yè)相類(lèi)似。它仍然需要 @ Page 指令(或等效的編程指令,它會(huì)將該頁(yè)的 AsyncMode 屬性設(shè)置為 true)中的 Async=“true” 屬性,而且它仍然與平時(shí)一樣通過(guò) PreRender 事件執(zhí)行,此時(shí)調(diào)用使用 RegisterAsyncTask 注冊(cè)的 Begin 方法,而且進(jìn)一步保持請(qǐng)求處理直到最后一個(gè)操作完成。例如,圖 7 中的代碼隱藏類(lèi)在功能上與圖 1 中的等效,但是它使用 RegisterTaskAsync 而非使用 AddOnPreRenderCompleteAsync。請(qǐng)注意名為 TimeoutAsyncOperation 的超時(shí)處理程序,如果 HttpWebRequest.BeginGetRequest 長(zhǎng)時(shí)間無(wú)法完成,將調(diào)用該處理程序。相應(yīng)的 .aspx 文件包括一個(gè)將超時(shí)間隔設(shè)置為 5 秒的 AsyncTimeout 屬性。還請(qǐng)注意傳給 RegisterAsyncTask 的第四個(gè)參數(shù)(可用于將數(shù)據(jù)傳送到 Begin 方法)的 null。
圖7
using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Net; using System.IO; using System.Text; using System.Text.RegularExpressions;
public partial class AsyncPageTask : System.Web.UI.Page { 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 = WebRequest.Create("http://msdn.microsoft.com"); return _request.BeginGetResponse(cb, state); }
void EndAsyncOperation(IAsyncResult ar) { string text; using (WebResponse response = _request.EndGetResponse(ar)) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { text = reader.ReadToEnd(); } }
Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase); MatchCollection matches = regex.Matches(text);
StringBuilder builder = new StringBuilder(1024); foreach (Match match in matches) { builder.Append(match.Groups[1]); builder.Append("<br/>"); }
Output.Text = builder.ToString(); }
void TimeoutAsyncOperation(IAsyncResult ar) { Output.Text = "Data temporarily unavailable"; } }
|
RegisterAsyncTask 的主要優(yōu)勢(shì)在于,它允許異步頁(yè)引發(fā)多個(gè)異步調(diào)用,并延遲呈現(xiàn)直到所有調(diào)用完成。它也很好地適用于單個(gè)異步調(diào)用,而且它提供了 AddOnPreRenderCompleteAsync 不具有的超時(shí)選項(xiàng)。如果生成一個(gè)只進(jìn)行一個(gè)異步調(diào)用的異步頁(yè),您可以使用 AddOnPreRenderCompleteAsync 或 RegisterAsyncTask。但對(duì)于放置兩個(gè)以上異步調(diào)用的異步頁(yè),RegisterAsyncTask 極大地簡(jiǎn)化了您的操作。
由于超時(shí)值是每頁(yè)而非每調(diào)用設(shè)置,因此您可能想知道是否能改變單個(gè)調(diào)用的超時(shí)值。簡(jiǎn)單的回答是否。您可以通過(guò)以編程方式修改頁(yè)的 AsyncTimeout 屬性,逐個(gè)請(qǐng)求地更改超時(shí),但是您無(wú)法將不同超時(shí)分配給從同一請(qǐng)求初始化的不同調(diào)用。
包裝它
現(xiàn)在,您已經(jīng)了解了 ASP.NET 2.0 中異步頁(yè)的實(shí)質(zhì)。它們?cè)诩磳⑼瞥龅?ASP.NET 版本中非常易于實(shí)現(xiàn),并且其體系結(jié)構(gòu)允許您在一個(gè)請(qǐng)求中批處理多個(gè)異步 I/O 操作,并延遲該頁(yè)的呈現(xiàn)直到所有操作完成。通過(guò)與異步 ADO.NET 和 .NET Framework 中的其他新異步功能相結(jié)合,異步 ASP.NET 頁(yè)針對(duì)因充滿(mǎn)線(xiàn)程池而限制可伸縮性的 I/O 綁定請(qǐng)求問(wèn)題提供了解決方案。
當(dāng)生成異步頁(yè)時(shí)最后需要注意的一點(diǎn)是,不應(yīng)該啟動(dòng)來(lái)自 ASP.NET 使用的同一線(xiàn)程池的異步操作。例如,在頁(yè)的異步點(diǎn)調(diào)用 ThreadPool.QueueUserWorkItem 會(huì)起反作用,因?yàn)樵摲椒▉?lái)自線(xiàn)程池,從而導(dǎo)致純粹獲取用于處理請(qǐng)求的零線(xiàn)程。相反,調(diào)用內(nèi)置于 Framework 中的異步方法(例如,方法 HttpWebRequest.BeginGetResponse 和 SqlCommand.BeginExecuteReader)通常認(rèn)為是安全的,因?yàn)檫@些方法傾向于使用完成端口實(shí)現(xiàn)異步行為。