熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

ASP.NET 2.0 中的異步頁功能應用

2013-11-13 09:45:12  來源: .NET編程 
ASPNET 提供了大量新功能其中包括聲明性數據綁定和母版頁成員和角色管理服務等但我認為最棒的功能是異步頁接下來讓我告訴您其中的原因

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

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

  ASPNET Beta 異步頁基礎結構的相關文檔很少讓我們展望一下異步頁的前景從而彌補這點不足請記住本專欄涉及 ASPNET 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 的方法那麼除了具有名為 FooBeginFoo 和 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 服務代理一起生成可通過 FooCompletedEventArgsResult 訪問 Foo 的返回值

  圖 展示使用 MethodAsync 模式異步調用 Web 服務的 GetTitles 方法的代碼隱藏類從功能上講該頁等同於圖 中的頁但其內部實現則大為不同AsyncWSInvokeaspx 包括一個 @ Page Async=true 指令類似於 AsyncWSInvokeaspx但是AsyncWSInvokeaspxcs 並不調用 AddOnPreRenderCompleteAsync它注冊一個用於 GetTitlesCompleted 事件的處理程序並調用 Web 服務代理上的 GetTitlesAsyncASPNET 仍然延遲呈現該頁直到 GetTitlesAsync 完成在內部當異步調用開始以及完成時它使用 SystemThreadingSynchronizationContext 的一個實例( 的一個新類)接收通知



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);

  // 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 將模擬區域性和 HttpContextCurrent 注入 MethodCompleted 事件處理程序而 AddOnPreRenderCompleteAsync 則不然其次如果該頁進行多個異步調用而且必須延遲呈現直到所有調用完成則使用 AddOnPreRenderCompleteAsync 要求您生成一個在所有調用完成前保持無信號狀態的 IasyncResult使用 MethodAsync這樣的操作就不是必需的您只需放置這些調用(數量不限)ASPNET 引擎延遲該呈現階段直到最後一個調用返回

異步任務

  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
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.