<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      在上篇博客【C#客戶端的異步操作】, 我介紹了一些.net中實現(xiàn)異步操作的方法,在那篇博客中,我是站在整個.net平臺的角度來講述各種異步操作的實現(xiàn)方式, 并針對各種異步操作以及不同的編程模型給出了一些參考建議。上篇博客談到的內(nèi)容可以算是異步操作的基礎(chǔ), 今天我再來談異步,專門來談在ASP.NET平臺下的各種異步操作。在這篇博客中,我主要演示在ASP.NET中如何使用各種異步操作。
      在后續(xù)博客中,我還會分析ASP.NET的源碼,解釋為什么可以這樣做,或者這樣的原因是什么,以解密內(nèi)幕的方式向您解釋這些操作的實現(xiàn)原理。

      由于本文是【C#客戶端的異步操作】的續(xù)集, 因此一些關(guān)于異步的基礎(chǔ)內(nèi)容,就不再過多解釋了。如不理解本文的示例代碼,請先看完那篇博文吧。

      【C#客戶端的異步操作】的結(jié)尾, 有一個小節(jié)【在Asp.net中使用異步】,我把我上次寫好的示例做了個簡單的介紹,今天我來專門解釋那些示例代碼。 不過,在寫博客的過程中,又做了一點補充,所以,請以前下載過示例代碼的朋友,你們需要重新下載那些示例代碼(還是那篇博客中)。
      說明:那些代碼都是在示范使用異步的方式調(diào)用【用Asp.net寫自己的服務(wù)框架】博客中所談到的那個服務(wù)框架, 且服務(wù)方法的代碼為:

      [MyServiceMethod]
      public static string ExtractNumber(string str)
      {
          // 延遲3秒,模擬一個長時間的調(diào)用操作,便于客戶演示異步的效果。
          System.Threading.Thread.Sleep(3000);
      
          if( string.IsNullOrEmpty(str) )
              return "str IsNullOrEmpty.";
      
          return new string((from c in str where Char.IsDigit(c) orderby c select c).ToArray());
      }
      

      在ASP.NET中使用異步

      我在【C#客戶端的異步操作】中提到一個觀點: 對于服務(wù)程序而言,異步處理可以提高吞吐量。什么是服務(wù)程序,簡單說來就是:可以響應(yīng)來自網(wǎng)絡(luò)請求的服務(wù)端程序。 我們熟悉的ASP.NET顯然是符合這個定義的。因此在ASP.NET程序中,適當(dāng)?shù)厥褂卯惒?/b>是可以提高服務(wù)端吞吐量的。 這里所說的適當(dāng)?shù)厥褂卯惒?/b>,一般是說:當(dāng)服務(wù)器的壓力不大且很多處理請求的執(zhí)行過程被阻塞在各種I/O等待(以網(wǎng)絡(luò)調(diào)用為主)操作上時, 而采用異步來減少阻塞工作線程的一種替代同步調(diào)用的方法。 反之,如果服務(wù)器的壓力已經(jīng)足夠大,或者沒有發(fā)生各種I/O等待,那么,在此情況下使用異步是沒有意義的。

      在.net中,幾乎所有的服務(wù)編程模型都是采用線程池處理請求任務(wù)的多線程工作模式。 自然地,ASP.NET也不例外,根據(jù)【C#客戶端的異步操作】的分析, 我們就不能再使用一些將阻塞操作交給線程池的方法了。比如:委托的異步調(diào)用,直接使用線程池,都是不可取的。 直接創(chuàng)建線程也是不合適的,因此那種方式會隨著處理請求的數(shù)量增大而創(chuàng)建一大堆線程,最后也將會影響性能。 因此,最終能被選用的只用BeginXxxxx/EndXxxxx方式。不過,我要補充的是:還有基于事件通知的異步模式也是一個不錯的選擇(我會用代碼來證明), 只要它是對原始BeginXxxxx/EndXxxxx方式的包裝。

      【用Asp.net寫自己的服務(wù)框架】中, 我說過,ASP.NET處理請求是采用了一種被稱為【管線】的方式,管線由HttpApplication控制并引發(fā)的一系列事件, 由HttpHandler來處理請求,而HttpModule則更多地是一種輔助角色。 還記得我在【C#客戶端的異步操作】 總結(jié)的異步特色嗎:【一路異步到底】。 ASP.NET的處理過程要經(jīng)過它們的處理,自然它們對于請求的處理也必須要支持異步。 幸運地是,這些負(fù)責(zé)請求處理的對象都是支持異步的。今天的博客也將著重介紹它們的異步工作方式。

      WebForm框架,做為ASP.NET平臺上最主要且默認(rèn)的開發(fā)框架,我自然也會全面地介紹它所支持的各種異步方式。
      MVC框架從2.0開始,也開始支持異步,本文也會介紹如何在這個版本中使用異步。

      該選哪個先出場呢?我想了很久,最后還是決定先請出處理請求的核心對象:HttpHandler

      異步 HttpHandler

      關(guān)于HttpHandler的接口,我在【用Asp.net寫自己的服務(wù)框架】中已有介紹, 這里就不再貼出它的接口代碼了,只想說一句:那是個同步調(diào)用接口,它并沒有異步功能。要想支持異步,則必須使用另一個接口:IHttpAsyncHandler

      // 摘要:
      //     定義 HTTP 異步處理程序?qū)ο蟊仨殞崿F(xiàn)的協(xié)定。
      public interface IHttpAsyncHandler : IHttpHandler
      {
          // 摘要:
          //     啟動對 HTTP 處理程序的異步調(diào)用。
          //
          // 參數(shù):
          //   context:
          //     一個 System.Web.HttpContext 對象,該對象提供對用于向 HTTP 請求提供服務(wù)的內(nèi)部服務(wù)器對象(如 Request、Response、Session
          //     和 Server)的引用。
          //
          //   extraData:
          //     處理該請求所需的所有額外數(shù)據(jù)。
          //
          //   cb:
          //     異步方法調(diào)用完成時要調(diào)用的 System.AsyncCallback。如果 cb 為 null,則不調(diào)用委托。
          //
          // 返回結(jié)果:
          //     包含有關(guān)進程狀態(tài)信息的 System.IAsyncResult。
          IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
          //
          // 摘要:
          //     進程結(jié)束時提供異步處理 End 方法。
          //
          // 參數(shù):
          //   result:
          //     包含有關(guān)進程狀態(tài)信息的 System.IAsyncResult。
          void EndProcessRequest(IAsyncResult result);
      }
      

      這個接口也很簡單,只有二個方法,并且與【C#客戶端的異步操作】 提到的BeginXxxxx/EndXxxxx設(shè)計方式差不多。如果這樣想,那么后面的事件就好理解了。
      在.net中,異步都是建立在IAsyncResult接口之上的,而BeginXxxxx/EndXxxxx是對這個接口最直接的使用方式。

      下面我們來看一下如何創(chuàng)建一個支持異步的ashx文件(注意:代碼中的注釋很重要)。

      public class AsyncHandler : IHttpAsyncHandler {
      
          private static readonly string ServiceUrl = "http://localhost:22132/service/DemoService/CheckUserLogin";
          
          public void ProcessRequest(HttpContext context)
          {
              // 注意:這個方法沒有必要實現(xiàn)。因為根本就不調(diào)用它。
              // 但要保留它,因為這個方法也是接口的一部分。
              throw new NotImplementedException();
          }
          
          public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
          {
              // 說明:
              //   參數(shù)cb是一個ASP.NET的內(nèi)部委托,EndProcessRequest方法將在那個委托內(nèi)部被調(diào)用。
              
              LoginInfo info = new LoginInfo();
              info.Username = context.Request.Form["Username"];
              info.Password = context.Request.Form["Password"];
      
              MyHttpClient<LoginInfo, string> http = new MyHttpClient<LoginInfo, string>();
              http.UserData = context;
      
              // ================== 開始異步調(diào)用 ============================
              // 注意:您所需要的回調(diào)委托,ASP.NET已經(jīng)為您準(zhǔn)備好了,直接用cb就好了。
              return http.BeginSendHttpRequest(ServiceUrl, info, cb, http);
              // ==============================================================
          }
      
          public void EndProcessRequest(IAsyncResult ar)
          {
              MyHttpClient<LoginInfo, string> http = (MyHttpClient<LoginInfo, string>)ar.AsyncState;
              HttpContext context = (HttpContext)http.UserData;
              
              context.Response.ContentType = "text/plain";
              context.Response.Write("AsyncHandler Result: ");
      
              try {
                  // ============== 結(jié)束異步調(diào)用,并取得結(jié)果 ==================
                  string result = http.EndSendHttpRequest(ar);
                  // ==============================================================
                  context.Response.Write(result);
              }
              catch( System.Net.WebException wex ) {
                  context.Response.StatusCode = 500;
                  context.Response.Write(HttpWebRequestHelper.SimpleReadWebExceptionText(wex));
              }
              catch( Exception ex ) {
                  context.Response.StatusCode = 500;
                  context.Response.Write(ex.Message);
              }
          }
      

      實現(xiàn)其實是比較簡單的,大致可以總結(jié)如下:
      1. 在BeginProcessRequest()方法,調(diào)用要你要調(diào)用的異步開始方法,通常會是另一個BeginXxxxx方法。
      2. 在EndProcessRequest()方法,調(diào)用要你要調(diào)用的異步結(jié)束方法,通常會是另一個EndXxxxx方法。
      真的就是這么簡單。

      這里要說明一下,在【C#客戶端的異步操作】中, 我演示了如何使用.net framework中的API去實現(xiàn)完整的異步發(fā)送HTTP請求的調(diào)用過程,但那個過程需要二次異步,而這個IHttpAsyncHandler接口卻只支持一次回調(diào)。 因此,對于這種情況,就需要我們自己封裝,將多次異步轉(zhuǎn)變成一次異步。以下是我包裝的一次異步的簡化版本:

      下面這個包裝類非常有用,我后面的示例還將會使用它。它也示范了如何創(chuàng)建自己的IAsyncResult封裝。因此建議仔細(xì)閱讀它。 (注意:代碼中的注釋很重要

      /// <summary>
      /// 對異步發(fā)送HTTP請求全過程的包裝類,
      /// 按IAsyncResult接口要求提供BeginSendHttpRequest/EndSendHttpRequest方法(一次回調(diào))
      /// </summary>
      /// <typeparam name="TIn"></typeparam>
      /// <typeparam name="TOut"></typeparam>
      public class MyHttpClient<TIn, TOut>
      {
          /// <summary>
          /// 用于保存額外的用戶數(shù)據(jù)。
          /// </summary>
          public object UserData;
      
          public IAsyncResult BeginSendHttpRequest(string url, TIn input, AsyncCallback cb, object state)
          {
              // 準(zhǔn)備返回值
              MyHttpAsyncResult ar = new MyHttpAsyncResult(cb, state);
      
              // 開始異步調(diào)用
              HttpWebRequestHelper<TIn, TOut>.SendHttpRequestAsync(url, input, SendHttpRequestCallback, ar);
              return ar;
          }
      
          private void SendHttpRequestCallback(TIn input, TOut result, Exception ex, object state)
          {
              // 進入這個方法表示異步調(diào)用已完成
              MyHttpAsyncResult ar = (MyHttpAsyncResult)state;
      
              // 設(shè)置完成狀態(tài),并發(fā)出完成通知。
              ar.SetCompleted(ex, result);
          }
          
          public TOut EndSendHttpRequest(IAsyncResult ar)
          {
              if( ar == null )
                  throw new ArgumentNullException("ar");
      
              // 說明:我并沒有檢查ar對象是不是與之匹配的BeginSendHttpRequest實例方法返回的,
              // 雖然這是不規(guī)范的,但我還是希望示例代碼能更簡單。
              // 我想應(yīng)該極少有人會亂傳遞這個參數(shù)。
      
              MyHttpAsyncResult myResult = ar as MyHttpAsyncResult;
              if( myResult == null )
                  throw new ArgumentException("無效的IAsyncResult參數(shù),類型不是MyHttpAsyncResult。");
      
              if( myResult.EndCalled )
                  throw new InvalidOperationException("不能重復(fù)調(diào)用EndSendHttpRequest方法。");
      
              myResult.EndCalled = true;
              myResult.WaitForCompletion();            
      
              return (TOut)myResult.Result;
          }
      }
      
      internal class MyHttpAsyncResult : IAsyncResult
      {
          internal MyHttpAsyncResult(AsyncCallback callBack, object state)
          {
              _state = state;
              _asyncCallback = callBack;
          }
      
          internal object Result { get; private set; }
          internal bool EndCalled;
      
          private object _state;
          private volatile bool _isCompleted;
          private ManualResetEvent _event;
          private Exception _exception;
          private AsyncCallback _asyncCallback;
      
      
          public object AsyncState
          {
              get { return _state; }
          }
          public bool CompletedSynchronously
          {
              get { return false; } // 其實是不支持這個屬性
          }
          public bool IsCompleted
          {
              get { return _isCompleted; }
          }
          public WaitHandle AsyncWaitHandle
          {
              get {
                  if( _isCompleted )
                      return null;    // 注意這里并不返回WaitHandle對象。
      
                  if( _event == null )     // 注意這里的延遲創(chuàng)建模式。
                      _event = new ManualResetEvent(false);
                  return _event;
              }
          }
      
          internal void SetCompleted(Exception ex, object result)
          {
              this.Result = result;
              this._exception = ex;
      
              this._isCompleted = true;
              ManualResetEvent waitEvent = Interlocked.CompareExchange(ref _event, null, null);
      
              if( waitEvent != null )
                  waitEvent.Set();        // 通知 EndSendHttpRequest() 的調(diào)用者
      
              if( _asyncCallback != null )
                  _asyncCallback(this);    // 調(diào)用 BeginSendHttpRequest()指定的回調(diào)委托
          }
      
          internal void WaitForCompletion()
          {
              if( _isCompleted == false ) {
                  WaitHandle waitEvent = this.AsyncWaitHandle;
                  if( waitEvent != null )
                      waitEvent.WaitOne();    // 使用者直接(非回調(diào)方式)調(diào)用了EndSendHttpRequest()方法。
              }
      
              if( _exception != null )
                  throw _exception;    // 將異步調(diào)用階段捕獲的異常重新拋出。
          }
      
          // 注意有二種線程競爭情況:
          //  1. 在回調(diào)線程中調(diào)用SetCompleted時,原線程訪問AsyncWaitHandle
          //  2. 在回調(diào)線程中調(diào)用SetCompleted時,原線程調(diào)用WaitForCompletion
      
          // 說明:在回調(diào)線程中,會先調(diào)用SetCompleted,再調(diào)用WaitForCompletion
      }
      

      對于這個包裝類來說,最關(guān)鍵還是MyHttpAsyncResult的實現(xiàn),它是異步模式的核心。

      ASP.NET 異步頁的實現(xiàn)方式

      從上面的異步HttpHandler可以看到,一個處理流程被分成二個階段了。但Page也是一個HttpHandler,不過,Page在處理請求時, 有著更復(fù)雜的過程,通常被人們稱為【頁面生命周期】,一個頁面生命周期對應(yīng)著一個ASPX頁的處理過程。 對于同步頁來說,整個過程從頭到尾連續(xù)執(zhí)行一遍就行了,這比較容易理解。但是對于異步頁來說,它必須要拆分成二個階段, 以下圖片反映了異步頁的頁面生命周期。注意右邊的流程是代表異步頁的。

      這個圖片是我從網(wǎng)上找的。原圖比較小,字體較模糊,我將原圖放大后又做了一番處理。本想在圖片中再加點說明, 考慮到尊重原圖作者,沒有在圖片上加上任何多余字符。下面我還是用文字來補充說明一下吧。

      在上面的左側(cè)部分是一個同步頁的處理過程,右側(cè)為一個異步頁的處理過程。
      這里尤其要注意的是那二個紅色塊的步驟:它們雖然只有一個Begin與End的操作, 但它們反映的是:在一個異步頁的【頁面生命周期】中,所有異步任務(wù)在執(zhí)行時所處的階段。 與HttpHandler不同,一個異步頁可以發(fā)起多個異步調(diào)用任務(wù)。 或許用所有這個詞也不太恰當(dāng),您就先理解為所有吧,后面會有詳細(xì)的解釋。

      引入這個圖片只是為了能讓您對于異步頁的執(zhí)行過程有個大致的印象: 它將原來一個線程連續(xù)執(zhí)行的過程分成以PreRender和PreRenderComplete為邊界的二段過程, 且可能會由二個不同的線程來分別處理它們。請記住這個邊界,下面在演示范例時我會再次提到它們。

      異步頁這個詞我已說過多次了,什么樣的頁面是一個異步頁呢?

      簡單說來,異步頁并不要求您要實現(xiàn)什么接口,只要在ASPX頁的Page指令中,加一個【Async="true"】的選項就可以了,請參考如下代碼:

      
      <%@ Page Language="C#" Async="true" AutoEventWireup="true" CodeFile="AsyncPage1.aspx.cs" Inherits="AsyncPage1" %>
      
      

      很簡單吧,再來看一下CodeFile中頁面類的定義:

      
      public partial class AsyncPage1 : System.Web.UI.Page
      
      

      沒有任何特殊的,就是一個普通的頁面類。是的,但它已經(jīng)是一個異步頁了。有了這個基礎(chǔ),我們就可以為它添加異步功能了。

      由于ASP.NET的異步頁有 3 種實現(xiàn)方式,我也將分別介紹它們。請繼續(xù)往下閱讀。

      1. 調(diào)用Page.AddOnPreRenderCompleteAsync()的異步頁

      在.net的世界里,許多支持異步的原始API都采用了Begin/End的設(shè)計方式,都是基于IAsyncResult接口的。 為了能方便地使用這些API,ASP.NET為它們設(shè)計了正好匹配的調(diào)用方式,那就是直接調(diào)用Page.AddOnPreRenderCompleteAsync()方法。 這個方法的名字也大概說明它的功能:添加一個異步操作到PreRenderComplete事件前。 我們還是來看一下這個方法的簽名吧:

      // 摘要:
      //     為異步頁注冊開始和結(jié)束事件處理程序委托。
      //
      // 參數(shù):
      //   state:
      //     一個包含事件處理程序的狀態(tài)信息的對象。
      //
      //   endHandler:
      //     System.Web.EndEventHandler 方法的委托。
      //
      //   beginHandler:
      //     System.Web.BeginEventHandler 方法的委托。
      //
      // 異常:
      //   System.InvalidOperationException:
      //     <async> 頁指令沒有設(shè)置為 true。- 或 -System.Web.UI.Page.AddOnPreRenderCompleteAsync(System.Web.BeginEventHandler,System.Web.EndEventHandler)
      //     方法在 System.Web.UI.Control.PreRender 事件之后調(diào)用。
      //
      //   System.ArgumentNullException:
      //     System.Web.UI.PageAsyncTask.BeginHandler 或 System.Web.UI.PageAsyncTask.EndHandler
      //     為空引用(Visual Basic 中為 Nothing)。
      public void AddOnPreRenderCompleteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      

      其中BeginEventHandler與EndEventHandler的定義如下:

      // 摘要:
      //     表示處理異步事件(如應(yīng)用程序事件)的方法。此委托在異步操作開始時調(diào)用。
      //
      // 返回結(jié)果:
      //     System.IAsyncResult,它表示 System.Web.BeginEventHandler 操作的結(jié)果。
      public delegate IAsyncResult BeginEventHandler(object sender, EventArgs e, AsyncCallback cb, object extraData);
      
      // 摘要:
      //     表示處理異步事件(如應(yīng)用程序事件)的方法。
      public delegate void EndEventHandler(IAsyncResult ar);
      

      如果單看以上接口的定義,可以發(fā)現(xiàn)除了“object sender, EventArgs e”是多余部分之外,其余部分則剛好與Begin/End的設(shè)計方式完全吻合,沒有一點多余。

      我們來看一下如何調(diào)用這個方法來實現(xiàn)異步的操作:(注意代碼中的注釋)

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          // 準(zhǔn)備回調(diào)數(shù)據(jù),它將由AddOnPreRenderCompleteAsync的第三個參數(shù)被傳入。
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = textbox1.Text;
      
          // 注冊一個異步任務(wù)。注意這三個參數(shù)哦。
          AddOnPreRenderCompleteAsync(BeginCall, EndCall, http);
      }
      
      private IAsyncResult BeginCall(object sender, EventArgs e, AsyncCallback cb, object extraData)
      {
          // 在這個方法中,
          // sender 就是 this
          // e 就是 EventArgs.Empty
          // cb 就是 EndCall
          // extraData 就是調(diào)用AddOnPreRenderCompleteAsync的第三個參數(shù)
          Trace.Write("BeginCall ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)extraData;
          
          // 開始一個異步調(diào)用。頁面線程也最終在執(zhí)行這個調(diào)用后返回線程池了。
          // 中間則是等待網(wǎng)絡(luò)的I/O的完成通知。
          // 如果網(wǎng)絡(luò)調(diào)用完成,則會調(diào)用 cb 對應(yīng)的回調(diào)委托,其實就是下面的方法
          return http.BeginSendHttpRequest(ServiceUrl, (string)http.UserData, cb, http);
      }
      
      private void EndCall(IAsyncResult ar)
      {
          // 到這個方法中,表示一個任務(wù)執(zhí)行完畢。
          // 參數(shù) ar 就是BeginCall的返回值。
      
          Trace.Write("EndCall ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)ar.AsyncState;
          string str = (string)http.UserData;
      
          try{
              // 結(jié)束異步調(diào)用,獲取調(diào)用結(jié)果。如果有異常,也會在這里拋出。
              string result = http.EndSendHttpRequest(ar);
              labMessage.Text = string.Format("{0} => {1}", str, result);
          }
          catch(Exception ex){
              labMessage.Text = string.Format("{0} => Error: {1}", str, ex.Message);
          }
      }
      

      對照一下異步HttpHandler中的介紹,你會發(fā)現(xiàn)它們非常像。

      如果要執(zhí)行多個異步任務(wù),可以參考下面的代碼:

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = textbox1.Text;
          AddOnPreRenderCompleteAsync(BeginCall, EndCall, http);
      
      
          MyHttpClient<string, string> http2 = new MyHttpClient<string, string>();
          http2.UserData = "T2_" + Guid.NewGuid().ToString();
          AddOnPreRenderCompleteAsync(BeginCall2, EndCall2, http2);
      }
      

      也很簡單,就是調(diào)用二次AddOnPreRenderCompleteAsync而已。

      前面我說過,異步的處理是發(fā)生在PreRender和PreRenderComplete之間,我們來還是看一下到底是不是這樣的。 在ASP.NET的Page中,我們很容易的輸出一些調(diào)試信息,且它們會顯示在所處的頁面生命周期的相應(yīng)執(zhí)行階段中。 這個方法很簡單,在Page指令中加上【Trace="true"】選項,并在頁面類的代碼中調(diào)用Trace.Write()或者Trace.Warn()就可以了。 下面來看一下我加上調(diào)試信息的頁面執(zhí)行過程吧。

      從這張圖片中,我們至少可以看到二個信息:
      1. 所有的異步任務(wù)的執(zhí)行過程確實發(fā)生在PreRender和PreRenderComplete之間。
      2. 所有的異步任務(wù)被串行地執(zhí)行了。

      2. 調(diào)用Page.RegisterAsyncTask()的異步頁

      我一直認(rèn)為ASP.NET程序也是一種服務(wù)程序,它要對客戶端瀏覽器發(fā)出的請求而服務(wù)。 由于是服務(wù),對于要服務(wù)的對象來說,都希望能盡快地得到響應(yīng),這其實也是對服務(wù)的一個基本的要求, 那就是:高吞量地快速響應(yīng)。

      對于前面所說的方法,顯然,它的所有異步任務(wù)都是串行執(zhí)行的,對于客戶端來說,等待的時間會較長。 而且,最嚴(yán)重的是,如果服務(wù)超時,上面的方法會一直等待,直到本次請求超時。 為了解決這二個問題,ASP.NET定義了一種異步任務(wù)類型:PageAsyncTask 。它可以解決以上二種問題。 首先我們還是來看一下PageAsyncTask類的定義:(說明:這個類的關(guān)鍵就是它的構(gòu)造函數(shù))

      // 摘要:
      //     使用并行執(zhí)行的指定值初始化 System.Web.UI.PageAsyncTask 類的新實例。
      //
      // 參數(shù):
      //   state:
      //     表示任務(wù)狀態(tài)的對象。
      //
      //   executeInParallel:
      //     指示任務(wù)能否與其他任務(wù)并行處理的值。
      //
      //   endHandler:
      //     當(dāng)任務(wù)在超時期內(nèi)成功完成時要調(diào)用的處理程序。
      //
      //   timeoutHandler:
      //     當(dāng)任務(wù)未在超時期內(nèi)成功完成時要調(diào)用的處理程序。
      //
      //   beginHandler:
      //     當(dāng)異步任務(wù)開始時要調(diào)用的處理程序。
      //
      // 異常:
      //   System.ArgumentNullException:
      //     beginHandler 參數(shù)或 endHandler 參數(shù)未指定。
      public PageAsyncTask(BeginEventHandler beginHandler, EndEventHandler endHandler, 
      			EndEventHandler timeoutHandler, object state, bool executeInParallel);
      

      注意這個構(gòu)造函數(shù)的簽名,它與AddOnPreRenderCompleteAsync()相比,多了二個參數(shù):EndEventHandler timeoutHandler, bool executeInParallel 。 它們的含義上面的注釋中有說明,這里只是提示您要注意它們而已。

      創(chuàng)建好一個PageAsyncTask對象后,只要調(diào)用頁面的RegisterAsyncTask()方法就可以注冊一個異步任務(wù)。 具體用法可參考我的如下代碼:(注意代碼中的注釋)

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          // 準(zhǔn)備回調(diào)數(shù)據(jù),它將由PageAsyncTask構(gòu)造函數(shù)的第四個參數(shù)被傳入。
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = textbox1.Text;
      
          // 創(chuàng)建異步任務(wù)
          PageAsyncTask task = new PageAsyncTask(BeginCall, EndCall, TimeoutCall, http);
          // 注冊異步任務(wù)
          RegisterAsyncTask(task);
      }
      
      private IAsyncResult BeginCall(object sender, EventArgs e, AsyncCallback cb, object extraData)
      {
          // 在這個方法中,
          // sender 就是 this
          // e 就是 EventArgs.Empty
          // cb 是ASP.NET定義的一個委托,我們只管在異步調(diào)用它時把它用作回調(diào)委托就行了。
          // extraData 就是PageAsyncTask構(gòu)造函數(shù)的第四個參數(shù)
          Trace.Warn("BeginCall ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)extraData;
      
          // 開始一個異步調(diào)用。
          return http.BeginSendHttpRequest(ServiceUrl, (string)http.UserData, cb, http);
      }
      
      private void EndCall(IAsyncResult ar)
      {
          // 到這個方法中,表示一個任務(wù)執(zhí)行完畢。
          // 參數(shù) ar 就是BeginCall的返回值。
          Trace.Warn("EndCall ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)ar.AsyncState;
          string str = (string)http.UserData;
      
          try {
              // 結(jié)束異步調(diào)用,獲取調(diào)用結(jié)果。如果有異常,也會在這里拋出。
              string result = http.EndSendHttpRequest(ar);
              labMessage.Text = string.Format("{0} => {1}", str, result);
          }
          catch( Exception ex ) {
              labMessage.Text = string.Format("{0} => Error: {1}", str, ex.Message);
          }
      }
      
      private void TimeoutCall(IAsyncResult ar)
      {
          // 到這個方法,就表示任務(wù)執(zhí)行超時了。
          Trace.Warn("TimeoutCall ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)ar.AsyncState;
          string str = (string)http.UserData;
      
          labMessage.Text = string.Format("{0} => Timeout.", str);
      }
      

      前面我說過PageAsyncTask是支持超時的,那么它的超時功能是如何使用的呢,上面的示例只是給了一個超時的回調(diào)委托而已。

      在開始演示PageAsyncTask的高級功能前,有必要說明一下示例所調(diào)用的服務(wù)端代碼。 本示例所調(diào)用的服務(wù)是【C#客戶端的異步操作】中使用的演示服務(wù), 服務(wù)代碼如下:

      [MyServiceMethod]
      public static string ExtractNumber(string str)
      {
          // 延遲3秒,模擬一個長時間的調(diào)用操作,便于客戶演示異步的效果。
          System.Threading.Thread.Sleep(3000);
      
          if( string.IsNullOrEmpty(str) )
              return "str IsNullOrEmpty.";
      
          return new string((from c in str where Char.IsDigit(c) orderby c select c).ToArray());
      }
      

      下面的示例我將演示開始二個異步任務(wù),并設(shè)置異步頁的超時時間為4秒鐘。

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          // 設(shè)置頁面超時時間為4秒
          Page.AsyncTimeout = new TimeSpan(0, 0, 4);
      
          // 注冊第一個異步任務(wù)
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = textbox1.Text;
          PageAsyncTask task = new PageAsyncTask(BeginCall, EndCall, TimeoutCall, http);
          RegisterAsyncTask(task);
      
          // 注冊第二個異步任務(wù)
          MyHttpClient<string, string> http2 = new MyHttpClient<string, string>();
          http2.UserData = "T2_" + Guid.NewGuid().ToString();
          PageAsyncTask task2 = new PageAsyncTask(BeginCall2, EndCall2, TimeoutCall2, http2);
          RegisterAsyncTask(task2);
      }
      

      此頁面的執(zhí)行過程如下:

      確實,第二個任務(wù)執(zhí)行超時了。

      再來看一下PageAsyncTask所支持的任務(wù)的并行執(zhí)行是如何調(diào)用的:

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          // 設(shè)置頁面超時時間為4秒
          Page.AsyncTimeout = new TimeSpan(0, 0, 4);
      
          // 注冊第一個異步任務(wù)
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = textbox1.Text;
          PageAsyncTask task = new PageAsyncTask(BeginCall, EndCall, TimeoutCall, http, true /*注意這個參數(shù)*/);
          RegisterAsyncTask(task);
      
          // 注冊第二個異步任務(wù)
          MyHttpClient<string, string> http2 = new MyHttpClient<string, string>();
          http2.UserData = "T2_" + Guid.NewGuid().ToString();
          PageAsyncTask task2 = new PageAsyncTask(BeginCall2, EndCall2, TimeoutCall2, http2, true /*注意這個參數(shù)*/);
          RegisterAsyncTask(task2);
      }
      

      此頁面的執(zhí)行過程如下:

      圖片清楚地反映出,這二個任務(wù)是并行執(zhí)行時,所以,這二個任務(wù)能在4秒內(nèi)同時執(zhí)行完畢。

      在結(jié)束對PageAsyncTask的介紹前,有必要對超時做個說明。 對于使用PageAsyncTask的異步頁來說,有二種方法來設(shè)置超時時間:
      1. 通過Page指令: asyncTimeout="0:00:45" ,這個值就是異步頁的默認(rèn)值。至于這個值的含義,我想您應(yīng)該懂的。
      2. 通過設(shè)置 Page.AsyncTimeout = new TimeSpan(0, 0, 4); 這種方式。示例代碼就是這種方式。
      注意:由于AsyncTimeout是Page級別的參數(shù),因此,它是針對所有的PageAsyncTask來限定的,并非每個PageAsyncTask的超時都是這個值。

      3. 基于事件模式的異步頁

      如果您看過我的博客【C#客戶端的異步操作】, 那么對【基于事件模式的異步】這個詞就不會再感到陌生了。在那篇博客中,我就對這種異步模式做過介紹, 只不是,上次是在WinForm程序中演示的而已。為了方便對比,我再次把那段代碼貼出來:

      /// <summary>
      /// 基于事件的異步模式
      /// </summary>
      /// <param name="str"></param>
      private void CallViaEvent(string str)
      {
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(str, str);
      }
      
      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          //bool flag = txtOutput.InvokeRequired;    // 注意:這里flag的值是false,也就是說可以直接操作UI界面
          if( e.Error == null ) 
              ShowResult(string.Format("{0} => {1}", e.UserState, e.Result));
          else
              ShowResult(string.Format("{0} => Error: {1}", e.UserState, e.Error.Message));        
      }
      

      上次,我就解釋過,這種方法在WinForm中非常方便。幸運的是,ASP.NET的異步頁也支持這種方式。
      ASP.NET的異步頁中的實現(xiàn)代碼如下:

      private void CallViaEvent(string str)
      {
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(str, str);
      }
      
      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          Trace.Warn("client_OnCallCompleted ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
      
          if( e.Error == null )
              labMessage.Text = string.Format("{0} => {1}", e.UserState, e.Result);
          else
              labMessage.Text = string.Format("{0} => Error: {1}", e.UserState, e.Error.Message);
      }
      

      搞什么呀,這二段代碼是一樣的嘛。 您是不是也有這樣的感覺呢?

      仔細(xì)看這二段代碼,還是能發(fā)現(xiàn)它們有區(qū)別的。這里我就不指出它們了。它們與異步無關(guān),說出它們意義不大, 反而,我更希望您對【基于事件模式的異步】留個好印象:它們就是一樣的。

      再來看一下如何發(fā)出多個異步任務(wù):

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
          string str = textbox1.Text;
      
          // 注意:這個異步任務(wù),我設(shè)置了2秒的超時。它應(yīng)該是不能按時完成任務(wù)的。
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, 2000);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(str, str);        // 開始第一個異步任務(wù)
          
      
          string str2 = "T2_" + Guid.NewGuid().ToString();
          MyAysncClient<string, string> client2 = new MyAysncClient<string, string>(ServiceUrl);
          client2.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted);
          client2.CallAysnc(str2, str2);        // 開始第二個異步任務(wù)
      }    
      
      void client2_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          ShowCallResult(2, e);
      
      
          // 再來一個異步調(diào)用
          string str3 = "T3_" + Guid.NewGuid().ToString();
          MyAysncClient<string, string> client3 = new MyAysncClient<string, string>(ServiceUrl);
          client3.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client3_OnCallCompleted);
          client3.CallAysnc(str3, str3);        // 開始第三個異步任務(wù)
      }
      

      頁面的執(zhí)行過程如下圖:

      這里要說明一下了:在【C#客戶端的異步操作】中我就給出這個類的實現(xiàn)代碼, 不過,這次我給它增加了超時功能,增加了一個重載的構(gòu)造函數(shù),需要在構(gòu)造函數(shù)的第二個參數(shù)傳入。 今天我就不貼出那個類的代碼了,有興趣的自己去下載代碼閱讀吧。 在上次貼的代碼,你應(yīng)該可以發(fā)現(xiàn),在CallAysnc()時,就已經(jīng)開始了異步操作。對于本示例來說,也就是在button1_click就已經(jīng)開始了二個異步操作。

      這是個什么意思呢?
      可以這樣來理解:前二個任務(wù)顯然是和LoadComplete,PreRender事件階段的代碼在并行執(zhí)行的。
      有意思的是:第三個任務(wù)是在第二個任務(wù)的結(jié)束事件中開始的,但三個任務(wù)的結(jié)束操作全在頁面的PreRender事件才得到處理。 下面我再把這個例子來改一下,就更有趣了:

      protected void button1_click(object sender, EventArgs e)
      {
          Trace.Write("button1_click ThreadId = " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
          string str = textbox1.Text;
      
          // 注意:這個異步任務(wù),我設(shè)置了2秒的超時。它應(yīng)該是不能按時完成任務(wù)的。
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, 2000);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(str, str);        // 開始第一個異步任務(wù)
      
          System.Threading.Thread.Sleep(3000);
      
          string str2 = "T2_" + Guid.NewGuid().ToString();
          MyAysncClient<string, string> client2 = new MyAysncClient<string, string>(ServiceUrl);
          client2.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted);
          client2.CallAysnc(str2, str2);        // 開始第二個異步任務(wù)
      }    
      

      現(xiàn)在,在第一個任務(wù)發(fā)出后,我讓線程等待了3秒,也就是等到了第一個任務(wù)的超時。然后再開始第二個任務(wù)。
      也就是說:在button1_click事件還沒執(zhí)行完畢,第一個任務(wù)就結(jié)束了。
      現(xiàn)在,您可以猜一下,此時的執(zhí)行過程是個什么樣的。

      猜好了就來看下圖吧。

      現(xiàn)在明白了吧:哪怕是在PostBackEvent階段就結(jié)束的任務(wù),也要等到PreRender之后才能得到處理。
      至于為什么會是這樣的,我以后再講。今天只要記住本文的第一張圖片就好了。
      我可是好不容易才找出這張圖片來的,且為了讓您能看得更清楚,還花了些時間修改了它。
      在那個圖片后面我還說過:在一個異步頁的【頁面生命周期】中,所有異步任務(wù)在執(zhí)行時所處的階段。 并在后面注明了這里的所有這個詞也不太恰當(dāng)。現(xiàn)在可以解釋為什么不恰當(dāng)了:
      【基于事件模式的異步】的開始階段并不一定要PreRender事件之后,而對于前二種異步面的實現(xiàn)方式則是肯定在PreRender事件之后。
      至于這其中的原因,同樣,您要等待我的后續(xù)博客了。

      各種異步頁的實現(xiàn)方式比較

      前面介紹了3種異步頁的實現(xiàn)方式,我打算在這里給它們做個總結(jié)及比較。當(dāng)然,這一切只代表我個人的觀點,僅供參考。

      為了能給出一個客觀的評價,我認(rèn)為先有必要再給個示例,把這些異步方式放在一起執(zhí)行,就好像把它們放在一起比賽一樣, 或許這樣會更有意思,同時也會讓我給出的評價更有說服力。

      在下面的示例中,我把上面說過的3種異步方式放在一起,并讓每種方法執(zhí)行多次(共10個異步任務(wù)),實驗代碼如下:

      protected void button1_click(object sender, EventArgs e)
      {        
          ShowThreadInfo("button1_click");
      
          // 為PageAsyncTask設(shè)置超時時間
          Page.AsyncTimeout = new TimeSpan(0, 0, 7);
      
          // 開啟4個PageAsyncTask,其中第1,4個任務(wù)不接受并行執(zhí)行,2,3則允許并行執(zhí)行
          Async_RegisterAsyncTask("RegisterAsyncTask_1", false);
          Async_RegisterAsyncTask("RegisterAsyncTask_2", true);
          Async_RegisterAsyncTask("RegisterAsyncTask_3", true);
          Async_RegisterAsyncTask("RegisterAsyncTask_4", false);
      
          // 開啟3個AddOnPreRenderCompleteAsync的任務(wù)
          Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_1");
          Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_2");
          Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_3");
      
          // 最后開啟3個基于事件通知的異步任務(wù),其中第2個任務(wù)由于設(shè)置了超時,將不能成功完成。
          Async_Event("MyAysncClient_1", 0);
          Async_Event("MyAysncClient_2", 2000);
          Async_Event("MyAysncClient_3", 0);
      }
      
      private void Async_RegisterAsyncTask(string taskName, bool executeInParallel)
      {
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = taskName;
          PageAsyncTask task = new PageAsyncTask(BeginCall_Task, EndCall_Task, TimeoutCall_Task, http, executeInParallel);
          RegisterAsyncTask(task);
      }
      private void Async_AddOnPreRenderCompleteAsync(string taskName)
      {
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = taskName;
          AddOnPreRenderCompleteAsync(BeginCall, EndCall, http);
      }
      private void Async_Event(string taskName, int timeoutMilliseconds)
      {
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, timeoutMilliseconds);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(taskName, taskName);
      }
      

      執(zhí)行過程如下圖:

      不知您看到這個執(zhí)行過程是否會想到為什么會是這個樣子的。至于為什么會是這個樣子的, 這就涉及到ASP.NET的異步頁的執(zhí)行過程,這個過程比較復(fù)雜,我以后再談。 今天咱們就來根據(jù)這個圖片來談?wù)劚容^表面化的東西,談一下這三種方式的差別。

      從上面的代碼以及執(zhí)行過程,可以看到一個有趣的現(xiàn)象,我明明是先注冊的4個PageAsyncTask 。 可是呢,最先顯示的卻是【BeginCall AddOnPreRenderCompleteAsync_1】。 我想我這里使用顯示這個詞也是比較恰當(dāng)?shù)模瑸槭裁茨兀恳驗椋仪懊嬉呀?jīng)解釋過了, 基于事件的異步的任務(wù)應(yīng)該是在button1_click事件處理器中先執(zhí)行的,只是我沒有讓它們顯示罷了。 接下來的故事也很自然,由于我將"MyAysncClient_2"設(shè)置為2秒的超時,它最先完成,只是結(jié)果為超時罷了。 緊接著,"MyAysncClient_1"和"MyAysncClient_3"也執(zhí)行結(jié)束了。嗯,是的:3個事件的異步任務(wù)全執(zhí)行完了。

      說到這里我要另起一段了,以提醒您的注意。
      有沒有注意到,前面說到的3個事件的異步任務(wù)全執(zhí)行完了。這個時候,其它的異步任務(wù)絕大部分還沒有開始呢, 它們3個咋就先執(zhí)行完了呢?

      有意思吧,其實何止3個,如果再來5個基于事件的異步任務(wù),它們還是會先執(zhí)行完成,不信的話,看下圖:

      或許舉這個例子把基于事件的異步方式捧高了。這里我也要客觀的解釋一下原因了:
      出現(xiàn)這個現(xiàn)象主要由2個原因造成的:
      1. 在這個例子中,"MyAysncClient_1", "MyAysncClient_2", "MyAysncClient_3", "AddOnPreRenderCompleteAsync_1" 由于都是異步任務(wù),所以基本上是并行執(zhí)行的,
      2. 由于3個基于事件的異步方式先執(zhí)行的,因此它們先結(jié)束了。

      接著來解釋圖片所反映的現(xiàn)象。當(dāng)基于事件的異步任務(wù)全執(zhí)行完成后," EndCall AddOnPreRenderCompleteAsync_1" 也被調(diào)用了。說明"AddOnPreRenderCompleteAsync_1"這個任務(wù)徹底地執(zhí)行完了。 接下來,"AddOnPreRenderCompleteAsync_2","AddOnPreRenderCompleteAsync_3"也依次執(zhí)行完了。

      我一開始用RegisterAsyncTask注冊的4個異步任務(wù)呢?終于,在前面的所有異步任務(wù)全部執(zhí)行完成后, 才開始了這類任務(wù)的執(zhí)行過程。首先執(zhí)行的是"RegisterAsyncTask_1",這個好理解。 接下來,"BeginCall RegisterAsyncTask_2", "BeginCall RegisterAsyncTask_3"被連續(xù)調(diào)用了, 這也好理解吧,因為我當(dāng)時創(chuàng)建異步任務(wù)時,指定它們是允許與其它任務(wù)并行執(zhí)行的,因此它們是一起執(zhí)行的。 3秒后,2個任務(wù)同時執(zhí)行完了,最后啟動了"RegisterAsyncTask_4",由于它不支持并行執(zhí)行,所以,它排在最后, 在沒有任何懸念中,"TimeoutCall RegisterAsyncTask_4"被調(diào)用了。這么正常啊,我設(shè)置過Page.AsyncTimeout = new TimeSpan(0, 0, 7); 因此,前二批PageAsyncTask趕在超時前正常結(jié)束了,留給"RegisterAsyncTask_4"的執(zhí)行時間只有1秒,它當(dāng)然就不能在指定時間內(nèi)正常完成。

      似乎到這里,這些異步任務(wù)的執(zhí)行過程都解釋完了,但是,有二個很奇怪的現(xiàn)象您有沒有發(fā)現(xiàn):
      1. 為什么AddOnPreRenderCompleteAsync的任務(wù)全執(zhí)行完了之后,才輪到PageAsyncTask的任務(wù)呢?
      2. 還有前面說過的,為什么是"BeginCall AddOnPreRenderCompleteAsync_1"最先顯示呢?
      這一切絕非偶然,如果您有興趣,可下載我的示例代碼,你運行千遍萬遍還將是這個結(jié)果。

      這些原因我以后再談,今天的博客只是想告訴您這樣一個結(jié)果就行了。
      不過,為了能讓您能容易地理解后面的內(nèi)容,我暫且告訴您:PageAsyncTask是建立在AddOnPreRenderCompleteAsync的基礎(chǔ)上的。

      有了前面這些實驗結(jié)果,我們再來對這3種異步頁方法做個總結(jié)及比較。

      1. AddOnPreRenderCompleteAsync: 它提供了最基本的異步頁的使用方法。就好像HttpHandler一樣,它雖能處理請求,但不太方便,顯得比較原始。 由于它提供的是比較原始的方法,您也可以自行包裝您的高級功能。

      2. PageAsyncTask: 與AddOnPreRenderCompleteAsync相比,它增加了超時以及并行執(zhí)行的功能,但我也說過,它是建立在AddOnPreRenderCompleteAsync的基礎(chǔ)之上的。 如果把AddOnPreRenderCompleteAsync比作為HttpHandler,那么PageAsyncTask則就像是Page 。因此它只是做了些高級的包裝罷了。

      3. 基于事件的異步方式:與前2者完全沒有關(guān)系,它只依賴于AspNetSynchronizationContext。這里有必要強調(diào)一下: 【基于事件的異步方式】可以理解為一個設(shè)計模式,也可以把它理解成對最基礎(chǔ)的異步方式的高級包裝。 它能提供或者完成的功能,依賴于包裝的方式及力度。 在我提供的這個包裝類中,它也可以實現(xiàn)與PageAsyncTask一樣的并行執(zhí)行以及超時功能。

      后二種方法功能強大的原因是來源于高級包裝,由于包裝,過程也會更復(fù)雜,因此性能或許也會有微小的損失。 如果您不能接受這點性能損失,可能還是選AddOnPreRenderCompleteAsync會比較合適。 不過,我要再次提醒您:它不支持并行執(zhí)行,不支持超時。

      請容忍我再夸一下【基于事件的異步模式】,從我前面的示例代碼,尤其是與WinForm中的示例代碼的比較中, 我們可以清楚的發(fā)現(xiàn),這種方式是非常易用的。掌握了這種方式,至少在這二大編程模型中都是適用的。 而且,它能在異步頁的執(zhí)行周期中,較早的進入異步等待狀態(tài),因此能更快的結(jié)束執(zhí)行過程。 想想【從"Begin Raise PostBackEvent"到"End PreRender"這中間還可以執(zhí)行多少代碼是不確定的】吧。

      【基于事件的異步模式】的優(yōu)點不僅如此,我的演示代碼中還演示了另一種用法: 在一個完成事件中,我還能再開啟另一個異步任務(wù)。 這個優(yōu)點使我可以有選擇性地啟動后續(xù)的異步操作。但是,這個特性是另2個不可能做到的! 這個原因可以簡單地表達(dá)為:在PreRender事件后,調(diào)用AddOnPreRenderCompleteAsync會拋異常。

      異步HttpModule的實現(xiàn)方式

      【用Asp.net寫自己的服務(wù)框架】中, 我示范過如果編寫一個HttpModule,通常只要我們實現(xiàn)IHttpModule接口,并在Init方法中訂閱一些事件就可以了:

      internal class DirectProcessRequestMoudle : IHttpModule
      {
          public void Init(HttpApplication app)
          {
              app.PostAuthorizeRequest += new EventHandler(app_PostAuthorizeRequest);
          }
      

      HttpHandler有異步接口的IHttpAsyncHandler,但HttpModule卻只有一個接口:IHttpModule,不管是同步還是異步。 異步HttpModule的實現(xiàn)方式并不是訂閱HttpApplication的事件,而是調(diào)用HttpApplication的一些注冊異步操作的方法來實現(xiàn)的(還是在Init事件中), 這些方法可參考以下列表:

      
      // 將指定的 System.Web.HttpApplication.AcquireRequestState 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.AcquireRequestState事件處理程序的集合。
      public void AddOnAcquireRequestStateAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.AuthenticateRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.AuthenticateRequest事件處理程序的集合。
      public void AddOnAuthenticateRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.AuthorizeRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.AuthorizeRequest事件處理程序的集合。
      public void AddOnAuthorizeRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.BeginRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.BeginRequest事件處理程序的集合。
      public void AddOnBeginRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.EndRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.EndRequest事件處理程序的集合。
      public void AddOnEndRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      public void AddOnLogRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      public void AddOnMapRequestHandlerAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostAcquireRequestState 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostAcquireRequestState事件處理程序的集合。
      public void AddOnPostAcquireRequestStateAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostAuthenticateRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostAuthenticateRequest事件處理程序的集合。
      public void AddOnPostAuthenticateRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostAuthorizeRequest 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostAuthorizeRequest事件處理程序的集合。
      public void AddOnPostAuthorizeRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      public void AddOnPostLogRequestAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostMapRequestHandler 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostMapRequestHandler事件處理程序的集合。
      public void AddOnPostMapRequestHandlerAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostReleaseRequestState 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostReleaseRequestState事件處理程序的集合。
      public void AddOnPostReleaseRequestStateAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostRequestHandlerExecute 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostRequestHandlerExecute事件處理程序的集合。
      public void AddOnPostRequestHandlerExecuteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostResolveRequestCache 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostResolveRequestCache事件處理程序的集合。
      public void AddOnPostResolveRequestCacheAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PostUpdateRequestCache 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PostUpdateRequestCache事件處理程序的集合。
      public void AddOnPostUpdateRequestCacheAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.PreRequestHandlerExecute 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.PreRequestHandlerExecute事件處理程序的集合。
      public void AddOnPreRequestHandlerExecuteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.ReleaseRequestState 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.ReleaseRequestState事件處理程序的集合。
      public void AddOnReleaseRequestStateAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.ResolveRequestCache 事件處理程序
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.ResolveRequestCache事件處理程序的集合。
      public void AddOnResolveRequestCacheAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      // 將指定的 System.Web.HttpApplication.UpdateRequestCache 事件
      // 添加到當(dāng)前請求的異步 System.Web.HttpApplication.UpdateRequestCache事件處理程序的集合。
      public void AddOnUpdateRequestCacheAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
      
      

      每個方法的含義從它們的名字是可以看出。 異步HttpModule的實現(xiàn)方式需要將異步對應(yīng)的Begin/End二個方法分別做為委托參數(shù)傳入這些方法中。
      注意:這些方法的簽名與Page.AddOnPreRenderCompleteAsync()是一致的,因此它們的具體用法也與Page.AddOnPreRenderCompleteAsync()一樣。

      為什么這里不設(shè)計成訂閱事件的方式?
      我想是因為:如果采用事件模式,調(diào)用者可以只訂閱其中的一個事件,ASP.NET不容易控制,還有"object state"這個參數(shù)不便于在訂閱事件時傳入。

      異步HttpModule的示例代碼如下:

      /// <summary>
      /// 【示例代碼】演示異步的HttpModule
      /// 說明:這個示例一丁點意義也沒有,純粹是為了演示。
      /// </summary>
      public class MyAsyncHttpModule  : IHttpModule
      {
          public static readonly object HttpContextItemsKey = new object();
      
          private static readonly string s_QueryDatabaseListScript =
              @"select dtb.name  from master.sys.databases as dtb order by 1";
      
          private static readonly string s_ConnectionString =
              @"server=localhost\sqlexpress;Integrated Security=SSPI;Asynchronous Processing=true";
      
      
          public void Init(HttpApplication app)
          {
              // 注冊異步事件
              app.AddOnBeginRequestAsync(BeginCall, EndExecuteReader, null);
          }
          
          private IAsyncResult BeginCall(object sender, EventArgs e, AsyncCallback cb, object extraData)
          {
              SqlConnection connection = new SqlConnection(s_ConnectionString);
              connection.Open();
      
              SqlCommand command = new SqlCommand(s_QueryDatabaseListScript, connection);
      
              CallbackParam cbParam = new CallbackParam {
                  Command = command,
                  Context = HttpContext.Current
              };
      
              return command.BeginExecuteReader(cb, cbParam);
          }
      
          private class CallbackParam
          {
              public SqlCommand Command;
              public HttpContext Context;
          }
      
          private void EndExecuteReader(IAsyncResult ar)
          {
              CallbackParam cbParam = (CallbackParam)ar.AsyncState;
              StringBuilder sb = new StringBuilder();
      
              try {
                  using( SqlDataReader reader = cbParam.Command.EndExecuteReader(ar) ) {
                      while( reader.Read() ) {
                          sb.Append(reader.GetString(0)).Append("; ");
                      }
                  }
              }
              catch( Exception ex ) {
                  cbParam.Context.Items[HttpContextItemsKey] = ex.Message;
              }
              finally {
                  cbParam.Command.Connection.Close();
              }
      
              if( sb.Length > 0 )
                  cbParam.Context.Items[HttpContextItemsKey] = "數(shù)據(jù)庫列表:" + sb.ToString(0, sb.Length - 2);
          }
      
          public void Dispose()
          {
          }
      }
      

      頁面可以使用如下方式獲得MyAsyncHttpModule的結(jié)果:

      public partial class TestMyAsyncHttpModule : System.Web.UI.Page
      {
          protected void Page_Load(object sender, EventArgs e)
          {
              string result = (string)HttpContext.Current.Items[MyAsyncHttpModule.HttpContextItemsKey]
                              ?? "沒有開啟MyAsyncHttpModule,請在web.config中啟用它。";
              Response.Write(result);
          }
      }
      

      說明:管線處理過程中,可能有多個HttpModule,但是異步的HttpModule在執(zhí)行時,只是在一個階段內(nèi),所有的HttpModule采用異步方式工作。 當(dāng)進入下一個階段前,必須要等到所有HttpModule全部在當(dāng)前階段內(nèi)執(zhí)行完畢。

      通常情況下,是沒有必要寫異步的HttpModule的。這是我寫的第一個異步HttpModule。

      異步的 Web Service

      由于Web Service也是受ASP.NET支持,且隨著ASP.NET一起出現(xiàn)。我們再來看一下如果將一個同步的服務(wù)方法改變成異步的方法。
      注意:將方法由同步改成異步版本,是不影響客戶端的。

      以下代碼是一個同步版本的服務(wù)方法:

      [WebMethod]
      public string ExtractNumber(string str)
      {
          //return ........
      }
      

      再來看一下最終的異步實現(xiàn)版本:

      [WebMethod]
      public IAsyncResult BeginExtractNumber(string str, AsyncCallback cb, object state)
      {
          MyHttpClient<string, string> http = new MyHttpClient<string, string>();
          http.UserData = "Begin ThreadId: " + Thread.CurrentThread.ManagedThreadId.ToString();
      
          return http.BeginSendHttpRequest(ServiceUrl, str, cb, http);
      }
      
      [WebMethod]
      public string EndExtractNumber(IAsyncResult ar)
      {
          MyHttpClient<string, string> http = (MyHttpClient<string, string>)ar.AsyncState;
          try{
              return http.EndSendHttpRequest(ar) +
                  ", " + http.UserData.ToString() +
                  ", End ThreadId: " + Thread.CurrentThread.ManagedThreadId.ToString();
          }
          catch(Exception ex){
              return ex.ToString();
          }
      }
      

      其實,要做的修改與IHttpHandler到IHttpAsyncHandler的工作差不多,在原有的同步方法后面加二個與異步操作有關(guān)的參數(shù), 并且返回值改為IAsyncResult,然后再添加一個EndXxxx方法就可以了,當(dāng)然了,EndXxxx方法的傳入?yún)?shù)只能是一個IAsyncResult類型的參數(shù)。

      ASP.NET MVC 中的異步方式

      在ASP.NET MVC框架中,感覺一下回到原始社會中,簡直和異步頁的封裝沒法比。來看代碼吧。(注意代碼中的注釋)

      // 實際可處理的Action名稱為 Test1 ,注意名稱后要加上 Async
      public void Test1Async()
      {
          // 告訴ASP.NET MVC,要開始一個異步操作了。
          AsyncManager.OutstandingOperations.Increment();
      
          string str = Guid.NewGuid().ToString();
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl);
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
          client.CallAysnc(str, str);        // 開始異步調(diào)用
      
      }
      
      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          // 告訴ASP.NET MVC,一個異步操作結(jié)束了。
          AsyncManager.OutstandingOperations.Decrement();
      
          if( e.Error == null )
              AsyncManager.Parameters["result"] = string.Format("{0} => {1}", e.UserState, e.Result);
          else
              AsyncManager.Parameters["result"] = string.Format("{0} => Error: {1}", e.UserState, e.Error.Message);
      
          // AsyncManager.Parameters["result"] 用于寫輸出結(jié)果。
          // 這里仍然采用類似ViewData的設(shè)計。
          // 注意:key 的名稱要和Test1Completed的參數(shù)名匹配。
      }
      
      // 注意名稱后要加上 Completed ,且其余部分與Test1Async的前綴對應(yīng)。
      public ActionResult Test1Completed(string result)
      {
          ViewData["result"] = result;
          return View();
      }
      

      說明:如果您認(rèn)為單獨為事件處理器寫個方法看起來不爽,您也可以采用匿名委托之類的閉包寫法,這個純屬個人喜好問題。

      再來個多次異步操作的示例:

      public void Test2Async()
      {
          // 表示要開啟3個異步操作。
          // 如果把這個數(shù)字設(shè)為2,極有可能會產(chǎn)生的錯誤的結(jié)果。不信您可以試一下。
          AsyncManager.OutstandingOperations.Increment(3);
      
          string str = Guid.NewGuid().ToString();
          MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl);
          client.UserData = "result1";
          client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted);
          client.CallAysnc(str, str);        // 開始第一個異步任務(wù)
      
          string str2 = "T2_" + Guid.NewGuid().ToString();
          MyAysncClient<string, string> client2 = new MyAysncClient<string, string>(ServiceUrl);
          client2.UserData = "result2";
          client2.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted);
          client2.CallAysnc(str2, str2);        // 開始第二個異步任務(wù)
      
          string str3 = "T3_" + Guid.NewGuid().ToString();
          MyAysncClient<string, string> client3 = new MyAysncClient<string, string>(ServiceUrl);
          client3.UserData = "result3";
          client3.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted);
          client3.CallAysnc(str3, str3);        // 開始第三個異步任務(wù)
      }
      
      void client2_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          // 遞減內(nèi)部的異步任務(wù)累加器。有點類似AspNetSynchronizationContext的設(shè)計。
          AsyncManager.OutstandingOperations.Decrement();
      
          MyAysncClient<string, string> client = (MyAysncClient<string, string>)sender;
          string key = client.UserData.ToString();
      
          if( e.Error == null )
              AsyncManager.Parameters[key] = string.Format("{0} => {1}", e.UserState, e.Result);
          else
              AsyncManager.Parameters[key] = string.Format("{0} => Error: {1}", e.UserState, e.Error.Message);
      }
      
      public ActionResult Test2Completed(string result1, string result2, string result3)
      {
          ViewData["result1"] = result1;
          ViewData["result2"] = result2;
          ViewData["result3"] = result3;
          return View();
      }
      

      我來解釋一下上面的代碼是如何以異步方式工作的。首先,我們要把Controller的基類修改為AsyncController,代碼如下:

      
      public class HomeController : AsyncController
      
      

      假如我有一個同步的Action方法:Test1,它看起來應(yīng)該是這樣的:

      public ActionResult Test1()
      {
          return View();
      }
      

      首先,我需要把它的返回值改成void, 并把方法名稱修改為Test1Async
      然后,在開始異步調(diào)用前,調(diào)用AsyncManager.OutstandingOperations.Increment();
      在異步完成時:
      1. 要調(diào)用AsyncManager.OutstandingOperations.Decrement();
      2. 將結(jié)果寫入到AsyncManager.Parameters[]這個集合中。注意key的名字后面要用到。

      到這里,異步開發(fā)的任務(wù)算是做了一大半了。你可能會想我在哪里返回ActionResult呢?
      再來創(chuàng)建一個Test1Completed方法,簽名應(yīng)該是這個樣子的:
      public ActionResult Test1Completed(string result)
      注意:方法中的參數(shù)名要和前面說過的寫AsyncManager.Parameters[]的key名一致,包括數(shù)量。
      再后面的事情,我想您懂的,我就不多說了。

      再來說說我對【ASP.NET MVC的異步方式】這個設(shè)計的感受吧。
      簡單說來就是:不夠完美。

      要知道在這個例子中,我可是采用的基于事件的異步模式啊,在異步頁中,哪有這些額外的調(diào)用?
      對于這個設(shè)計,我至少有2點不滿意:
      1. AsyncManager.OutstandingOperations.Increment(); Decrement();由使用者來控制,容易出錯。
      2. AsyncManager.Parameters[]這個bag設(shè)計方式也不爽,難道僅僅是為了簡單?因為我可以在完成事件時,根據(jù)條件繼續(xù)后面的異步任務(wù),最終結(jié)果可能并不確定,因此后面的XXXXCompleted方法的簽名就是個問題了。

      為什么在ASP.NET MVC中,這個示例需要調(diào)用Increment(); Decrement(),而在異步頁中不需要呢?
      恐怕有些人會對此有好奇,我就告訴大家吧:這與AspNetSynchronizationContext有關(guān)。

      AspNetSynchronizationContext,真是個【成也蕭何,敗成蕭何】的東西,在異步頁為什么不需要我們調(diào)用類似Increment(); Decrement()的語句是因為, 它內(nèi)部也有個這樣的累加器,不過,當(dāng)時在設(shè)計基于事件的異步模式時,在ASP.NET運行環(huán)境中,SynchronizationContext就是使用了AspNetSynchronizationContext這個具體實現(xiàn)類, 但它的絕大部分成員卻是internal類型的。如果可以使用它,可以用一種簡便地方式設(shè)置一個統(tǒng)一的回調(diào)委托:

      
      if( this._syncContext.PendingOperationsCount > 0 ) {
          this._syncContext.SetLastCompletionWorkItem(this._callHandlersThreadpoolCallback);
      }
      
      

      就這么一句話,可以不用操心使用者到底開始了多少個異步任務(wù),都可以在所有的異步結(jié)束后,回調(diào)指定的委托。只是可惜的是,這二個成員都是internal的!

      如果當(dāng)初微軟設(shè)計AspNetSynchronizationContext時,不開放SetLastCompletionWorkItem這個方法, 是擔(dān)心使用者亂調(diào)用導(dǎo)致ASP.NET運行錯誤的話,現(xiàn)在ASP.NET MVC的這種設(shè)計顯然更容易出錯。 當(dāng)然了,ASP.NET MVC出來的時候,這一切早就出現(xiàn)了,因此它也無法享受AspNetSynchronizationContext的便利性。 不過,最讓我想不通的是:直到ASP.NET 4.0,這一切還是原樣。 難道是因為ASP.NET MVC獨立在升級,連InternalsVisibleTo的機會也不給它嗎?

      就算我們不用基于事件的異步模式,異步頁還有二種實現(xiàn)方法呢(都不需要累加器),可是ASP.NET MVC卻沒有實現(xiàn)類似的功能。 所以,這樣就顯得很不完善。我們也只能期待未來的版本能改進這些問題了。

      MSDN參考文章:在 ASP.NET MVC 中使用異步控制器

      受爭論的【基于事件的異步模式】

      本來在我的寫作計劃中,是沒有這段文字的,可就在我打算發(fā)布這篇博客之前,想到上篇博客中的評論,突然我想到一本書:CLR via C# 。 是的,就是這本書,我想很多人手里有這本書,想到這本書是因為上篇博客的評論中,出現(xiàn)一個與我的觀點有著不一致的聲音(來自AndersTan),而他應(yīng)該是Jeffer Richter的粉絲。 我早就買了這本書了(中文第三版),其實也是AndersTan推薦的,不過一直沒有看完, 因此,根本就沒有發(fā)現(xiàn)Jeffer Richter是【基于事件的異步模式】的反對者, 這個可參考書中696頁。Jeffer Richter在書中說:“由于我不是EAP的粉絲,而且我不贊同使用這個模式, 所以一直沒有花太多的時間在它上面。然而,我知道有一些人確實喜歡這個模式,而且想使用它,所以我專門花了一些時間研究它。” 為了表示對大牛的敬重,我用藍(lán)色字體突出他說的話(當(dāng)然是由周靖翻譯的)。看到這句話以及后面他對于此模式的評價,尤其是在 【27.11.2 APM和EAP的對比】這個小節(jié)中對于EAP的評價,讓我感覺大牛其實也沒有很好地了解這個模式。

      這里再補充一下,書中提到二個英文簡寫:EAP: Event-base Asynchronous Pattern, APM: Asynchronous Programming Model 。書中689頁中,Jeffer Richter還說過:“雖然我是APM的超級粉絲,但是我必須承認(rèn)它存在的一些問題。” 與之相反,雖然我不是APM的忠實粉絲,我卻不認(rèn)為他所說的問題真的是APM的缺點。他說的第一點,感覺就沒有意義。 我不知道有多少人在現(xiàn)實使用中,是在調(diào)用了Begin方法后,立即去調(diào)用End方法? 我認(rèn)為.net允許這種使用方式,可能還是更看中的是使用上的靈活性,畢竟微軟要面對的開發(fā)者會有千奇百怪的要求。 而且MSDN中也解釋了這種調(diào)用會阻塞線程。訪問IAsyncResult是可以得到一個WaitHandle對象, 這個好像在上篇博客的評論中有人也提過了,我當(dāng)時也不想說了,這次就把我的實現(xiàn)方式貼出來了,只希望告訴一些人:這個成員雖然是個耗資源的東西, 但要看你如何去實現(xiàn)它了:有些時候(異步完成的時候)可以返回null的,所以,通常應(yīng)該設(shè)計成一種延遲創(chuàng)建模式才對(我再一次的提醒:在設(shè)計它時要考慮多線程的并發(fā)訪問)。

      剛才扯遠(yuǎn)了,我們還是來說關(guān)于Jeffer Richter對于【27.11.2 APM和EAP的對比】這個小節(jié)的看法(699頁)。這個小節(jié)有4個段話,分別從4個方面說了些EAP的【缺點】, 我也將依次來發(fā)表我的觀點。

      1. Jeffer Richter認(rèn)為EAP的最大優(yōu)點在于和IDE的配合使用,且在后面一直提到GUI線程。 顯然EAP模式被代表了,被WinForm這類桌面程序程序代表了。 我今天的示例代碼全部是可以在ASP.NET環(huán)境下運行的,而且還特意和WinForm下的使用方法做了比較,結(jié)果是:使用方式基本相同。 我認(rèn)為這個結(jié)果才是EAP模式最大的優(yōu)點:在不同的編程模型中不必考慮線程模型問題。

      2. Jeffer Richter說:事實上,EAP必須為引發(fā)的所有進度報告和完成事件分配從EventArgs派生的對象......。 看到這句話的感覺還是和上句話差不多:被代表了。 對于這段話,我認(rèn)為有必要從幾個角度來表達(dá)我的觀點:
      a. 進度報告:我想問一句:ASP.NET編程模型下進度報告有什么意義,或者說如何實現(xiàn)? 在我今天演示的示例代碼中,我一直沒演示進度報告吧?事實上,我的包裝類中根本就不提供這個功能,只提供了完成事件的通知功能。 再說,為什么需要進度報告?因為桌面程序需要,它們?yōu)榱四茏尦绦驌碛懈玫挠脩趔w驗。當(dāng)然也可以不提供進度報告嘛, 大不了讓用戶守在電腦面前傻等就是了,這樣還會有性能損失嗎?當(dāng)然沒有,但是用戶可能會罵人......。
      b. 性能損失:MyAysncClient是對一個更底層的靜態(tài)方法調(diào)用的封裝。我也很明白:有封裝就有性能的損失。但我想:一次異步任務(wù)也就只通知一次,性能損失能有多大? 而且明知道有性能損失,我為什么還要封裝呢?只為一個很簡單的理由:使用起來更容易!
      c. 對象的回收問題:如果按照J(rèn)effer Richter的說法,多創(chuàng)建這幾個對象就讓GC為難的話,會讓我對.NET失去信心,連ASP.NET也不敢用了, 因為:要知道.NET的世界是完全面向?qū)ο蟮氖澜纾淮蜽EB請求的處理過程中,ASP.NET不知道要創(chuàng)建多少個對象,我真的數(shù)不清楚。

      3. Jeffer Richter說:如果在登記事件處理方法之前調(diào)用XxxAsync方法,......。看到這里,我笑了。 顯然,大牛是非常討厭EAP模式的。EAP是使用了事件,這個錯誤的調(diào)用順序問題如果是EAP的錯,那么.NET的事件模式就是個錯誤的設(shè)計。 大牛說這句真是不負(fù)責(zé)任嘛。

      4. Jeffer Richter說:“EAP的錯誤處理和系統(tǒng)的其余部分也不一致,首先,異步不會拋出。在你的事件處理方法中,必須查詢;AsyncCompletedEventArgs的Exception屬性,看它是不是null ......” 看到這句話,我突然想到:一個月前在同事的桌上看到Jeffery Zhao 在【2010第二屆.NET技術(shù)與IT管理技術(shù)大會 的一個 The Evolution of Async Programming on .NET Platform】培訓(xùn)PPT,代碼大致是這樣寫的:

      class XxxCompletedEventArgs : EventArgs {
          Exception Error { get; }
          TResult Result { get; }
      }
      

      所以,我懷疑:Jeffer Richter認(rèn)為EAP模式在完成時的事件中,異常也結(jié)果也是這樣分開來處理的!

      大家不妨回想一下,回到Jeffery Richter所說的APM模式下,我們?yōu)榱四艿玫疆惒秸{(diào)用的結(jié)果,去調(diào)用End方法, 結(jié)果呢,如果異步在處理時,有異常發(fā)生了,此時會拋出來。是的,我也同意使用這種方式來明確的告之調(diào)用者:此時沒有結(jié)果,只有異常。

      我們還是再來看一下我前面一直使用的一段代碼:

      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          if( e.Error == null )
              labMessage.Text = string.Format("{0} => {1}", e.UserState, e.Result);
          else
              labMessage.Text = string.Format("{0} => Error: {1}", e.UserState, e.Error.Message);
      }
      

      表面上看,這段代碼確實有Jeffer Richter所說的問題:有異常不會主動拋出。
      這里有必要說明一下:有異常不主動拋出,而是依賴于調(diào)用者判斷返回結(jié)果的設(shè)計方式,是不符合.NET設(shè)計規(guī)范的。 那我如果把代碼寫成下面的這樣呢?

      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          try {
              labMessage.Text = string.Format("{0} => {1}", e.UserState, e.Result);
          }
          catch( Exception ex ) {
              labMessage.Text = string.Format("{0} => Error: {1}", e.UserState, ex.Message);
          }
      }
      

      什么,您不認(rèn)為我直接訪問e.Result,會出現(xiàn)異常嗎?

      再來看一下我寫的事件參數(shù)類型吧,看看我是如何做的:

      public class CallCompletedEventArgs : AsyncCompletedEventArgs
      {
          private TOut _result;
      
          public CallCompletedEventArgs(TOut result, Exception e, bool canceled, object state)
              : base(e, canceled, state)
          {
              _result = result;
          }
      
          public TOut Result
          {
              get
              {
                  base.RaiseExceptionIfNecessary();
                  return _result;
              }
          }
      }
      

      其中,RaiseExceptionIfNecessary()方法的實現(xiàn)如下(微軟實現(xiàn)的):

      protected void RaiseExceptionIfNecessary()
      {
          if( this.Error != null ) {
              throw new TargetInvocationException(SR.GetString("Async_ExceptionOccurred"), this.Error);
          }
          if( this.Cancelled ) {
              throw new InvalidOperationException(SR.GetString("Async_OperationCancelled"));
          }
      }
      

      讓我們再來看前面的EAP模式中完成事件中的標(biāo)準(zhǔn)處理代碼

      void client_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e)
      {
          if( e.Error == null )
              labMessage.Text = string.Format("{0} => {1}", e.UserState, e.Result);
          else
              labMessage.Text = string.Format("{0} => Error: {1}", e.UserState, e.Error.Message);
      }
      

      的確,這種做法對于EAP模式來說:是標(biāo)準(zhǔn)的處理方式:首先要判斷this.Error != null ,為什么這個 不規(guī)范 的方式會成為標(biāo)準(zhǔn)呢?

      我要再問一句:為什么不用try.....catch這種更規(guī)范的處理方式呢?

      顯然,我也演示了:EAP模式在獲取結(jié)果時,也可以支持try.....catch這種方式的。在這里不用它的理由是因為:
      相對于if判斷這類簡單的操作來說,拋異常是個【昂貴】的操作。這種明顯可以提高性能的做法,難道有錯嗎?
      在.net設(shè)計規(guī)范中,還有Tester-Doer, Try-Parse這二類模式。我想很多人也應(yīng)該用過的吧,設(shè)計它們也是因為性能問題,與EAP的理由是一樣的。

      再來總結(jié)一下。我的CallCompletedEventArgs類在實現(xiàn)時,有二個關(guān)鍵點:
      1. 事件類型要從AsyncCompletedEventArgs繼承。
      2. 用只讀屬性返回結(jié)果,但在訪問前,要調(diào)用基類的base.RaiseExceptionIfNecessary();
      這些都是EAP模式中,正確的設(shè)計方式。什么是模式?這就是模式。什么是規(guī)范?這就是規(guī)范!

      我們不能因為錯誤的設(shè)計,或者說,不尊守規(guī)范的設(shè)計,而造成的缺陷也要怪罪于EAP 。

      結(jié)束語

      異步是個很有用的技術(shù),不管是對于桌面程序還是服務(wù)程序都是很用意義的。

      不過,相對于同步調(diào)用來說,異步也是復(fù)雜的,但它的各種使用方式也是很精彩的。

      異步很精彩,故事沒講完,請繼續(xù)關(guān)注我的后續(xù)博客。

      點擊此處轉(zhuǎn)到下載示例代碼頁面

      posted on 2011-11-20 20:19  Fish Li  閱讀(52147)  評論(91)    收藏  舉報
      主站蜘蛛池模板: 欧美黑人性暴力猛交在线视频| 狠狠综合久久综合88亚洲爱文| 国产精品一区二区中文| 干老熟女干老穴干老女人| 国产色无码专区在线观看| 色偷偷亚洲女人天堂观看| 国内精品久久久久影院网站| 日韩人妻无码精品久久| 免费A级毛片无码A∨蜜芽试看| 开心一区二区三区激情| 精品少妇后入一区二区三区 | 国产福利深夜在线播放| 91中文字幕一区在线| 日韩在线视频观看免费网站| 欧美成年性h版影视中文字幕| 免费国产一级特黄aa大片在线| 天干天干夜啦天干天干国产| 九九电影网午夜理论片| 国产首页一区二区不卡| 国产成人自拍小视频在线| 视频一区二区三区四区不卡| 人成午夜免费大片| 2021亚洲va在线va天堂va国产| 精品91在线| 日韩丝袜欧美人妻制服| 777米奇影视第四色| 国产亚洲精品自在久久vr| 老鸭窝在钱视频| 成年午夜免费韩国做受视频| 国产av中文字幕精品| 苏尼特右旗| 亚洲AV蜜桃永久无码精品| 日区中文字幕一区二区| 欧美人与禽2o2o性论交| 18岁日韩内射颜射午夜久久成人| 韩国无码AV片在线观看网站| 办公室强奷漂亮少妇视频| 精品久久丝袜熟女一二三| 色综合天天综合天天综| 国产精品永久久久久久久久久| 最新中文字幕国产精品|