ASP.NET 2.0 中的非同步頁(from MSDN)

來源:互聯網
上載者:User
ASP.NET 2.0 中的非同步頁 發布日期: 2006-4-19 | 更新日期: 2006-4-19

下載本文的代碼: WickedCode0510.exe (123KB)

本頁內容
ASP.NET 1.x 中的非同步頁
ASP.NET 2.0 中的非同步頁
非同步資料繫結
非同步呼叫 Web 服務
非同步任務
封裝它

ASP.NET 2.0 提供了大量新功能,其中包括聲明性資料繫結和主版頁面,成員和角色管理服務等。但我認為最棒的功能是非同步頁,接下來讓我告訴您其中的原因。

當 ASP.NET 接收針對頁的請求時,它從線程池中提取一個線程並將請求分配給該線程。一個普通的(或同步的)頁在該請求期間保留線程,從而防止該線程用於處理其他請求。如果一個同步請求成為 I/O 綁定(例如,如果它調用一個遠程 Web 服務或查詢一個遠端資料庫,並等待調用返回),那麼分配給該請求的線程在調用返回之前處於掛起狀態。這影響了延展性,原因是線程池的可用線程是有限的。如果所有請求處理線程全部阻塞以等待 I/O 操作完成,則其他請求排入隊列等待線程釋放。最好的情況是輸送量減少,因為請求等待較長的時間才能得到處理。最壞的情況則是該隊列填滿,並且 ASP.NET 因 503“Server Unavailable”錯誤使後續請求失敗。

非同步頁為由 I/O 綁定的請求引起的問題提供優秀的解決方案。頁處理從線程池線程開始,但是當一個非同步 I/O 操作開始響應 ASP.NET 的訊號之後,該線程返回線程池。當該操作完成時,ASP.NET 從線程池提取另一個線程,並完成該請求的處理。由於線程池線程得到了更高效的使用,因此提高了延展性。那些掛起等待 I/O 完成的線程現在可用於服務其他請求。直接的受益方是不執行長時間 I/O 操作並因此可以快速進出管線的請求。長時間等待進入管線會對此類請求的效能帶來不小的負面影響。

ASP.NET 2.0 Beta 2 非同步頁基礎結構的相關文檔很少。讓我們展望一下非同步頁的前景,從而彌補這點不足。請記住,本專欄涉及 ASP.NET 2.0 和 .NET Framework 2.0 的測試版本。

ASP.NET 1.x 中的非同步頁

ASP.NET 1.x 本質上不支援非同步頁,但是通過堅韌的努力和不懈地創新可以產生非同步頁。有關更多概述資訊,請參閱 MSDNMagazine 2003 年 6 月刊的文章“Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code”,該文章的作者是 Fritz Onion。

這裡的技巧是,在一個頁的程式碼後置類別中實現 IhttpAsyncHandler,從而提示 ASP.NET 通過調用 IHttpAsyncHandler.BeginProcessRequest 來處理請求,而不是通過調用該頁的 IHttpHandler.ProcessRequest 方法。然後,您的 BeginProcessRequest 實現可以啟動另一個線程。該線程調用 base.ProcessRequest,使得頁進入其常規請求處理生命週期(完成諸如 Load 和 Render 的事件),但是在非 ThreadPool 線程上例外。同時,啟動新線程之後 BeginProcessRequest 立即返回,從而允許執行 BeginProcessRequest 的線程返回線程池。

這是基本思想,但細節中還有很多注意事項。其中,您需要實現 IAsyncResult,並從 BeginProcessRequest 中返回它。這通常意味著建立一個 ManualResetEvent 對象,並且當 ProcessRequest 在後台線程中返回時向其發送訊號。此外,您必須提供調用 base.ProcessRequest 的線程。遺憾的是,多數用於將工作移到後台線程的常規技術(包括 Thread.Start、ThreadPool.QueueUserWorkItem 和非同步委託)在 ASP.NET 應用程式中都是起反作用的,因為它們或者從線程池“偷盜”線程,或者有不受限制的線程增長的危險。正確的非同步頁實現使用自訂線程池,但自訂線程池類不容易編寫(有關更多資訊,請參閱 MSDN Magazine 2005 年 2 月刊的 .NET Matters 專欄)。

主要是在 ASP.NET 1.x 中產生非同步頁並非不可能,而是有些乏味。在嘗試一、兩次之後,您不禁會想一定會有更好的方法。目前,這個好方法就是 ASP.NET 2.0。

返回頁首

ASP.NET 2.0 中的非同步頁

ASP.NET 2.0 極大地簡化了產生非同步頁的方式。首先使用該頁的 @ Page 指令引入 Async=“true” 屬性,如下所示:

在後台,這會通知 ASP.NET 在該頁中實現 IhttpAsyncHandler。接下來,您在該頁生存期的早期(例如,在 Page_Load 時)調用新的 Page.AddOnPreRenderCompleteAsync 方法來註冊一個 Begin 方法和一個 End 方法,如以下代碼所示:

AddOnPreRenderCompleteAsync (    new BeginEventHandler(MyBeginMethod),    new EndEventHandler (MyEndMethod));

接下來的操作比較有趣。該頁經曆其常規處理生命週期,直到 PreRender 事件剛剛引發之後。然後,ASP.NET 調用使用 AddOnPreRenderCompleteAsync 註冊的 Begin 方法。Begin 方法的任務是啟動諸如資料庫查詢或 Web 服務調用的非同步作業,並立即返回。此時,分配給該請求的線程返回到線程池。此外,Begin 方法返回 IAsyncResult,它允許 ASP.NET 確定非同步作業完成的時間,這個時候 ASP.NET 從線程池提取線程並調用 End 方法。當 End 返回之後,ASP.NET 執行該頁生命週期其餘的部分,包括呈現階段。在 Begin 返回以及調用 End 之間,該請求處理線程可以自由地服務於其他請求,直至調用 End 且延遲呈現為止。由於 2.0 版的 .NET Framework 提供多種執行非同步作業的方式,因此,您甚至無需實現 IasyncResult。反之,Framework 替您實現。

1 中的程式碼後置類別提供一個樣本。響應頁包含一個 ID 為“Output”的 Label 控制項。該頁使用 System.Net.HttpWebRequest 類提取 http://MSDN.microsoft.com 的內容。然後,它分析返回的 HTML,並將它發現的全部 HREF 目標列表寫出到 Label 控制項。

由於 HTTP 要求需要較長時間才能返回,因此,AsyncPage.aspx.cs 非同步執行對它的處理。它在 Page_Load 中註冊 Begin 和 End 方法,並且在 Begin 方法中,它調用 HttpWebRequest.BeginGetResponse 啟用一個非同步 HTTP 要求。BeginAsyncOperation 將由 BeginGetResponse 返回的 IAsyncResult 返回到 ASP.NET,導致當 HTTP 要求完成時,ASP.NET 調用 EndAsyncOperation。EndAsyncOperation 進而分析該內容並將結果寫入 Label 控制項,之後進行呈現,並且 HTTP 響應返回到瀏覽器。

圖 2 同步和非同步頁處理

圖 2 說明 ASP.NET 2.0 同步和非同步頁之間的區別。當請求同步頁時,ASP.NET 為該請求分配線程池中的一個線程,並在該線程上執行頁。如果該請求停止執行 I/O 操作,則掛起線程,直到完成操作,從而可以完成該頁的生命週期。相反,非同步頁通常通過 PreRender 事件執行。然後,調用使用 AddOnPreRenderCompleteAsync 註冊的 Begin 方法,之後,該請求處理線程返回線程池。Begin 啟動一個非同步 I/O 操作,當該操作完成時,ASP.NET 從線程池提取另一個線程並調用 End 方法,並且在該線程上執行該頁生命週期的其餘部分。

圖 3 跟蹤輸出顯示非同步頁的非同步點

對 Begin 的調用標記該頁的“非同步點”。圖 3 中的跟蹤準確顯示非同步點發生在何處。如果調用,則必須在非同步點之前調用 AddOnPreRenderCompleteAsync — 即,不晚於該頁的 PreRender 事件。

返回頁首

非同步資料繫結

通常情況下,ASP.NET 頁並不使用 HttpWebRequest 直接請求其他頁,但它們通常查詢資料庫並對結果進行資料繫結。因此,您將如何使用非同步頁執行非同步資料繫結呢? 4 中的程式碼後置類別顯示進行此操作的一種方式。

AsyncDataBind.aspx.cs 與 AsyncPage.aspx.cs 使用相同的 AddOnPreRenderCompleteAsync 模式。但是,AsyncDataBind.aspx.cs 的 BeginAsyncOperation 方法調用 ADO.NET 2.0 中的新方法 SqlCommand.BeginExecuteReader(而非 HttpWebRequest.BeginGetResponse),以執行一個非同步資料庫查詢。當調用完成時,EndAsyncOperation 調用 SqlCommand.EndExecuteReader 以擷取 SqlDataReader,然後將其儲存在私人欄位中。在用於 PreRenderComplete 事件(在非同步作業完成但呈現該頁之前引發)的事件處理常式中,AsyncDataBind.aspx.cs 之後將 SqlDataReader 綁定到 Output GridView 控制項。從外觀上看,該頁類似於使用 GridView 呈現資料庫查詢結果的普通(同步)頁。但是在內部,該頁更具延展性,因為它並不掛起線程池線程以等待查詢返回。

返回頁首

非同步呼叫 Web 服務

另一個通常由 ASP.NET Web 頁執行的、與 I/O 相關的任務是調出 Web 服務。由於 Web 服務調用花費較長時間才能返回,因此,執行它們的頁是用於非同步處理的理想選擇。

5 顯示產生調出 Web 服務的非同步頁的方式。它使用 1 4 中相同的 AddOnPreRenderCompleteAsync 機制。該頁的 Begin 方法通過調用 Web 服務代理的非同步 Begin 方法啟動一個非同步 Web 服務調用。該頁的 End 方法在私人欄位中緩衝對 Web 方法返回的 DataSet 的引用,並且 PreRenderComplete 處理常式將 DataSet 綁定到 GridView。作為參考,該調用的目標 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 服務代理支援兩種對 Web 服務進行非同步呼叫的機制。一個是 .NET Framework 1.x 和 2.0 Web 服務代理中的每方法 Begin 和 End 方法。另一個是僅由 .NET Framework 2.0 的 Web 服務代理提供的新 MethodAsync 方法和 MethodCompleted 事件。

如果一個 Web 服務有一個名為 Foo 的方法,那麼除了具有名為 Foo、BeginFoo 和 EndFoo 的方法外,.NET Framework 版本 2.0 Web 服務代理還包括名為 FooAsync 的方法和名為 FooCompleted 的事件。可以通過註冊 FooCompleted 事件的處理常式並調用 FooAsync 來非同步呼叫 Foo,如下所示:

proxy.FooCompleted += new FooCompletedEventHandler (OnFooCompleted);proxy.FooAsync (...);...void OnFooCompleted (Object source, FooCompletedEventArgs e){    // Called when Foo completes}

當非同步呼叫由於 FooAsync 完成而開始時,將引發 FooCompleted 事件,從而導致調用 FooCompleted 事件處理常式。封裝該事件處理常式 (FooCompletedEventHandler) 的委託和傳遞給它的第二個參數 (FooCompletedEventArgs) 都隨 Web 服務代理一起產生。可通過 FooCompletedEventArgs.Result 訪問 Foo 的傳回值。

6 展示使用 MethodAsync 模式非同步呼叫 Web 服務的 GetTitles 方法的程式碼後置類別。從功能上講,該頁等同於 5 中的頁。但其內部實現則大為不同。AsyncWSInvoke2.aspx 包括一個 @ Page Async=“true” 指令,類似於 AsyncWSInvoke1.aspx。但是,AsyncWSInvoke2.aspx.cs 並不調用 AddOnPreRenderCompleteAsync;它註冊一個用於 GetTitlesCompleted 事件的處理常式,並調用 Web 服務代理上的 GetTitlesAsync。ASP.NET 仍然延遲呈現該頁,直到 GetTitlesAsync 完成。在內部,當非同步呼叫開始以及完成時,它使用 System.Threading.SynchronizationContext 的一個執行個體(2.0 的一個新類)接收通知。

使用 MethodAsync 而非 AddOnPreRenderCompleteAsync 實現非同步頁有兩個優勢。首先,MethodAsync 將類比、地區性和 HttpContext.Current 注入 MethodCompleted 事件處理常式,而 AddOnPreRenderCompleteAsync 則不然。其次,如果該頁進行多個非同步呼叫,而且必須延遲呈現直到所有調用完成,則使用 AddOnPreRenderCompleteAsync 要求您產生一個在所有調用完成前保持無訊號狀態的 IasyncResult。使用 MethodAsync,這樣的操作就不是必需的;您只需放置這些調用(數量不限),ASP.NET 引擎延遲該呈現階段,直到最後一個調用返回。

返回頁首

非同步任務

MethodAsync 是從非同步頁進行多個非同步 Web 服務調用並延遲呈現階段直到所有調用完成的一個簡便方法。但如果您想在一個非同步頁中執行若干非同步 I/O 操作,而且這些操作不涉及 Web 服務,那該如何呢? 這麼說,可以反過來產生一個 IAsyncResult,它可以返回到 ASP.NET 以允許它瞭解最後一個調用何時完成的嗎? 幸運的是,答案是否定的。

在 ASP.NET 2.0 中,System.Web.UI.Page 類引入了另一個方法來簡化非同步作業: RegisterAsyncTask。RegisterAsyncTask 比 AddOnPreRenderCompleteAsync 具有四個優勢。首先,除了 Begin 和 End 方法,RegisterAsyncTask 還允許您註冊當非同步作業長時間無法完成時調用的逾時方法。您可以通過在該頁的 @ Page 指令中包含 AsyncTimeout 屬性以聲明性方式設定逾時。AsyncTimeout="5" 將逾時設定為 5 秒。第二個優勢是,您可以在一個請求中多次調用 RegisterAsyncTask 來註冊若干非同步作業。和使用 MethodAsync 一樣,ASP.NET 延遲呈現該頁,直到所有操作完成。第三,您可以使用 RegisterAsyncTask 的第四個參數將狀態傳遞給 Begin 方法。最後,RegisterAsyncTask 將類比、地區性和 HttpContext.Current 注入 End 和 Timeout 方法。正如本文前面提到的,使用 AddOnPreRenderCompleteAsync 註冊的 End 方法的情況則不然。

在其他方面,依賴於 RegisterAsyncTask 的非同步頁與依賴於 AddOnPreRenderCompleteAsync 的非同步頁相類似。它仍然需要 @ Page 指令(或等效的編程指令,它會將該頁的 AsyncMode 屬性設定為 true)中的 Async=“true” 屬性,而且它仍然與平時一樣通過 PreRender 事件執行,此時調用使用 RegisterAsyncTask 註冊的 Begin 方法,而且進一步保持請求處理直到最後一個操作完成。例如, 7 中的程式碼後置類別在功能上與 1 中的等效,但是它使用 RegisterTaskAsync 而非使用 AddOnPreRenderCompleteAsync。請注意名為 TimeoutAsyncOperation 的逾時處理常式,如果 HttpWebRequest.BeginGetRequest 長時間無法完成,將調用該處理常式。相應的 .aspx 檔案包括一個將逾時間隔設定為 5 秒的 AsyncTimeout 屬性。還請注意傳給 RegisterAsyncTask 的第四個參數(可用於將資料傳送到 Begin 方法)的 null。

RegisterAsyncTask 的主要優勢在於,它允許非同步頁引發多個非同步呼叫,並延遲呈現直到所有調用完成。它也很好地適用於單個非同步呼叫,而且它提供了 AddOnPreRenderCompleteAsync 不具有的逾時選項。如果產生一個只進行一個非同步呼叫的非同步頁,您可以使用 AddOnPreRenderCompleteAsync 或 RegisterAsyncTask。但對於放置兩個以上非同步呼叫的非同步頁,RegisterAsyncTask 極大地簡化了您的操作。

由於逾時值是每頁而非每調用設定,因此您可能想知道是否能改變單個調用的逾時值。簡單的回答是否。您可以通過以編程方式修改頁的 AsyncTimeout 屬性,逐個請求地更改逾時,但是您無法將不同逾時分配給從同一請求初始化的不同調用。

返回頁首

封裝它

現在,您已經瞭解了 ASP.NET 2.0 中非同步頁的實質。它們在即將推出的 ASP.NET 版本中非常易於實現,並且其體繫結構允許您在一個請求中批處理多個非同步 I/O 操作,並延遲該頁的呈現直到所有操作完成。通過與非同步 ADO.NET 和 .NET Framework 中的其他新非同步功能相結合,非同步 ASP.NET 頁針對因充滿線程池而限制延展性的 I/O 綁定請求問題提供瞭解決方案。

當產生非同步頁時最後需要注意的一點是,不應該啟動來自 ASP.NET 使用的同一線程池的非同步作業。例如,在頁的非同步點調用 ThreadPool.QueueUserWorkItem 會起反作用,因為該方法來自線程池,從而導致純粹擷取用於處理請求的零線程。相反,調用內建於 Framework 中的非同步方法呼叫(例如,方法 HttpWebRequest.BeginGetResponse 和 SqlCommand.BeginExecuteReader)通常認為是安全的,因為這些方法傾向於使用完成連接埠實現非同步行為。

請將針對 Jeff 的問題和評論發送到 wicked@microsoft.com

Jeff ProsiseMSDN Magazine 的資深編輯以及若干書籍的作者,其中包括 Programming Microsoft .NET (Microsoft Press, 2002)。他也是 Wintellect(一家軟體諮詢和教育公司)的創始人之一。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.