閱讀742 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF技術剖析之十一:異步操作在WCF中的應用(上篇)

按照操作執行所需的資源類型,我們可以將操作分為CPU綁定型(CPU Bound)操作和I/O綁定型(I/O Bound)操作。對於前者,操作的執行主要利用CPU進行密集的計算,而對於後者,大部分的操作處理時間花在I/O操作處理,比如訪問數據庫、文件係統、網絡資源等。對於I/O綁定型操作,我們可以充分利用多線程的機製,讓多個操作在自己的線程並發執行,從而提高係統性能和響應能力。服務調用就是典型的I/O綁定型操作,所以多線程在服務調用中具有廣泛的應用。在本篇文章中,我們專門來討論多線程或者是異步操作在WCF中的具體應用。

如果按照異步操作發生的位置,我個人將WCF應用的異步操作分為下麵3種變體。

  • 異步信道調用:客戶端通過綁定創建的信道向服務端發送消息,從而實現了對服務的調用,不管消息通過信道向服務端發送的方式是同步的(采用請求-回複MEP進行消息交換)還是異步的(采用單向MEP進行消息交換),客戶端程序都可以通過代理對象異步地調用信道,從而實現異步服務調用;
  • 單向(One-way)消息交換:客戶端的信道通過單向的消息交換模式向服務端發送消息,消息一旦抵達傳輸層馬上返回,從而達到異步服務調用的效果;
  • 異步服務實現:服務端在具體實現服務操作的時候,采用異步調用的方式。

圖1清晰地揭示了以上3種異步場景在整個服務調用中所發生的時機。對於這3種典型的異步操作,它們之間是相互獨立的。對於單向消息交換,由於在上麵一節中已經進行過詳細的介紹,在本節中主要介紹其餘兩種異步操作的具體使用。本篇文章我們著重探討第一種形式(異步信道調用)的異步調用,關於異步服務的實現放在下篇中。

clip_image002

圖1 WCF多線程應用的三種典型場景

為了方便客戶端進行異步的服務調用,最簡便的方式就通過SvcUtil.exe這個代碼生成工具幫助我們生成機遇異步調用的服務代理類。由於SvcUtil.exe同時也為VS提供了添加服務引用的實現,異步服務代理也可以通過添加服務引用的方式創建。在具體通過服務代理進行異步服務調用的時候,可以采用不同的調用形式,不僅可以采用參數典型的BeginXxx和EndXxx的形式,也可以采用回調(Callback)的形式,還可以采用事件注冊的形式。

對於任何一個服務操作,不管它是否采用了異步的實現方式,也不管是否采用單向的消息交換模式,我們均可以通過添加服務引用或者直接使用SvcUtil.exe的方式創建異步服務代理,對服務進行異步調用。

如果通過添加服務引用的方式來創建異步服務代理,隻需要在添加服務引用對話框中點擊“高級(Advanced)”按鈕,便會彈出如下一個“服務引用設置(Service Reference Settings)”對話框,勾選“生成異步操作(Generate asynchronous operations)”複選框即可,如圖2所示。

clip_image004

圖2 添加服務引用時生成異步操作的設置

通過這種方式生成的代理類與沒有選擇“生成異步操作”選項一樣,都是生成一個繼承自ClientBase<TChannel>的類,所不同的是,該類中會多出一些與異步服務調用相關的成員。我們同樣以我們的CalculatorService為例(服務契約的定義如下)。

   1: [ServiceContract(Namespace="urn:artech.com")]
   2: public interface ICalculator
   3: {
   4:     [OperationContract]
   5:     double Add(double x, double y);
   6: }

通過這種方式生成的代理類CalculateClient會多出下麵列出的事件和方法成員。

   1: public partial class CalculateClient : ClientBase< ICalculator>, ICalculator
   2: {
   3:     //其他成員
   4:     public event System.EventHandler<AddCompleteEventArgs> AddComplete;
   5:     public IAsyncResult BeginAdd(double x, double y, AsyncCallback callback, object asyncState)
   6:     {
   7:         //省略實現
   8:     }
   9:     
  10:     public double EndAdd(System.IAsyncResult result)
  11:     {
  12:         //省略實現
  13:     }
  14:     
  15:     public void AddAsync(double x, double y)
  16:     {
  17:         //省略實現
  18:     }
  19:  
  20:     public void AddAsync(double x, double y, object userState)
  21:     {
  22:         //省略實現
  23:     }
  24: }

事件AddComplete將在Add操作執行之後觸發,你可以注冊該事件,在運算結束之後做一些特殊的工作,比如運算結果的顯示。該事件包含一個特殊的EventArgs:AddCompleteEventArgs。該事件參數類型同樣是通過添加服務引用自動創建的。AddCompleteEventArgs繼承自System.ComponentModel.AsyncCompleteEventArgs。在事件處理器中可以通過該參數得到異步方法執行的結果(Result屬性)和異步操作執行過程中拋出的異常(Error屬性),以及得到在執行異步操作顯式指定的信息(UserState)。AddCompleteEventArgs和AsyncCompleteEventArgs的定義如下。

   1: public partial class AddCompleteEventArgs : AsyncCompleteEventArgs
   2: {
   3:  
   4:     public AddCompleteEventArgs(object[] results,Exception exception, bool cancelled, object userState) :
   5:         base(exception, cancelled, userState)
   6:     {
   7:         //省略實現
   8:     }
   9:  
  10:     public double Result
  11:     {
  12:         get
  13:         {
  14:            //省略實現
  15:         }
  16:     }
  17: }

 

   1: public class AsyncCompleteEventArgs : EventArgs
   2: {
   3:     public bool Cancelled { get; }
   4:     public Exception Error { get; }
   5:     public object UserState { get; }
   6: }

接下來我將介紹3種不同的執行異步服務調用的方式,為了簡單起見,我們以上麵提到的CalculatorService為例演示通過異步操作得到運算結果,並將結果輸出。首先采用傳統的異步編程模式BeginXxx/EndXxx,如下麵的代碼所示,在調用BeginAdd方法後,可以做一些額外的處理工作,這些工作將會和Add服務操作的調用並發地運行,最終的運算結果通過EndAdd方法得到。

   1: CalculateClient proxy = new CalculateClient();
   2: IAsyncResult asynResult = proxy.BeginAdd(1, 2, null, null);
   3: //其他操作
   4: double result = proxy.EndAdd(asynResult);
   5: proxy.Close();
   6: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);

通過上麵的方式進行異步調用有一個不好的地方,就是當EndAdd方法被執行的時候,如果異步執行的方法Add沒有執行結束的話,該方法將會阻塞當前線程並等待異步方法的結束,往往不能起到地多線程並發執行應有的作用。我們真正希望的是在異步執行結束後自動回調設定的操作,這樣就可以采用回調的方式來實現這樣的機製了。

在下麵的代碼中,我們通過一個匿名方法的形式定義回調操作,由於在回調操用中輸出運算結果時需要使用到參與運算的操作數,我們通過BeginAdd方法的最後一個object類型參數實現向回調操作傳遞數據,在回調操作中通過IAsyncResult對象的AsyncState獲得。

   1: CalculateClient proxy = new CalculateClient();
   2: proxy.BeginAdd(1, 2,
   3:     delegate(IAsyncResult asyncResult)
   4:     {
   5:         double[] operands = asyncResult.AsyncState as double[];
   6:         double result = proxy.EndAdd(asyncResult);
   7:         proxy.Close();
   8:         Console.WriteLine("x + y = {2} when x = {0} and y = {1}", operands[0], operands[1], result);
   9:     }, new double[]{1,2});

實際上,事件注冊和通過回調從表現上看比較類似,當操作結束之後,對於前者通過觸發事件的方式執行相應的操作,而對於後者直接執行指定的回調操作。如果采用事件注冊的方式,上麵的代碼就可以改寫成下麵的形式。通過AddAsync開始異步操作,如果需要向AddComplete事件傳遞數據,可以使用該方法的第3個參數userState(該參數和BeginAdd的第4個參數asyncState具有相似的作用),設定的值可以通過AddCompleteEventArgs的UserState屬性獲得,而操作執行的結果則通過AddCompleteEventArgs的Result屬性獲得。

   1: CalculateClient proxy = new CalculateClient();
   2: proxy.AddComplete += delegate(object sender, AddCompleteEventArgs args)
   3: {
   4:     double[] operands = args.UserState as double[];
   5:     double result = args.Result;
   6:     proxy.Close();
   7:     Console.WriteLine("x + y = {2} when x = {0} and y = {1}", operands[0], operands[1], result);
   8: };
   9: proxy.AddAsync(1, 2,new double[]{1,2}); 

作者:Artech

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 14:03:52

  上一篇:go  Jenkins 應用三十六計-插件信息[轉載]
  下一篇:go  WCF技術剖析之十一:異步操作在WCF中的應用(下篇)