閱讀314 返回首頁    go 技術社區[雲棲]


實踐重於理論——創建一個監控程序探測WCF的並發處理機製

由於WCF的並發是針對某個封裝了服務實例的InstanceContext而言的(參考《並發的本質》《並發中的同步》),所以在不同的實例上下文模式下,會表現出不同的並發行為。接下來,我們從具體的實例上下文模式的角度來剖析WCF的並發處理機製,如果對WCF實例上下文模式和實例上下文提供機製不了解的話,請參閱《WCF技術剖析(卷1)》第9章。

為了使讀者對采用不同實例上下文對並發的影響有一個深刻的認識,會創建一個簡單的WCF應用,並在此基礎上添加監控功能,主要監控各種事件的執行時間,比如客戶端服務調用的開始和結束時間,服務操作開始執行和結束執行的時間等等。讀者可以根據實時輸出的監控信息,對WCF的並發處理情況有一個很直觀的認識。 [源代碼從這裏下載]

一、服務契約定義

本實例依然采用我們熟悉的四層結構,即契約、服務、寄宿和客戶端。為了以可視化的形式實時輸出監控信息,對於客戶端和服務寄宿程序均采用Windows Form應用類型。我們依然以計算服務作為例子,下麵是服務契約的定義。

   1: using System.ServiceModel;
   2: namespace Artech.ConcurrentServiceInvocation.Service.Interface
   3: {
   4:     [ServiceContract(Namespace="https://www.artech.com/")]
   5:     public interface ICalculator
   6:     {
   7:         [OperationContract]
   8:         double Add(double x, double y);
   9:     }
  10: }

二、創建監控器:EventMonitor

由於我們需要監控各種事件的時間,所以我定義了一個名為EventType的枚舉表示不同的事件類型。8個枚舉值分別表示開始和結束服務調用(客戶端)、開始和結束服務操作執行(服務端)、開始和結束回調(服務端)以及開始和結束回調操作的執行(客戶端)。關於回調的事件枚舉選項在本例中不會需要,主要是為了後續演示的需要。

   1: using System;
   2: namespace Artech.ConcurrentServiceInvocation.Service.Interface
   3: {
   4:     public enum EventType
   5:     { 
   6:         StartCall,
   7:         EndCall,
   8:         StartExecute,
   9:         EndExecute,
  10:         StartCallback,
  11:         EndCallback,
  12:         StartExecuteCallback,
  13:         EndExecuteCallback
  14:     }
  15: }

然後我定義了如下一個EventMonitor的靜態類,該類通過兩個重載的Send方法觸發事件的形式發送事件通知。我定義了專門的事件參數類型MonitorEventArgs,封裝客戶端ID、事件類型和觸發時間。Send具有兩個重載,一個具有用整數表示的客戶端ID,另一個沒有。前者用於客戶端,可以顯式指定客戶端ID,後者需要從客戶端手工添加的消息報頭提取客戶端ID,該消息報頭的名稱和命名空間通過兩個常量定義。

   1: using System;
   2: using System.ServiceModel;
   3: namespace Artech.ConcurrentServiceInvocation.Service.Interface
   4: {
   5:     public static class EventMonitor
   6:     {
   7:         public const string CientIdHeaderNamespace = "https://www.artech.com/";
   8:         public const string CientIdHeaderLocalName = "ClientId";
   9:         public static EventHandler<MonitorEventArgs> MonitoringNotificationSended;
  10:  
  11:         public static void Send(EventType eventType)
  12:         {
  13:             if (null != MonitoringNotificationSended)
  14:             { 
  15:                 int clientId = OperationContext.Current.IncomingMessageHeaders.GetHeader<int>(CientIdHeaderLocalName,CientIdHeaderNamespace);                
  16:                 MonitoringNotificationSended(null,new MonitorEventArgs(clientId,eventType,DateTime.Now));
  17:             }
  18:         }
  19:  
  20:         public static void Send(int clientId, EventType eventType)
  21:         {
  22:             if (null != MonitoringNotificationSended)
  23:             { 
  24:                 MonitoringNotificationSended(null,new MonitorEventArgs(clientId,eventType,DateTime.Now));
  25:             }
  26:         }        
  27:     }
  28:  
  29:     public class MonitorEventArgs : EventArgs
  30:     {
  31:         public int ClientId{ get; private set; }
  32:         public EventType EventType{ get; private set; }
  33:         public DateTime EventTime{ get; private set; }
  34:  
  35:         public MonitorEventArgs(int clientId, EventType eventType, DateTime eventTime)
  36:         {
  37:             this.ClientId = clientId;
  38:             this.EventType = eventType;
  39:             this.EventTime = eventTime;
  40:         }
  41:     }
  42: }

三、創建服務類型:CalculatorService

EventMonitor的Send方法可以直接用在CalculatorService的Add操作方法中,實時輸出操作方法開始和結束執行的時間,已經當前處理的客戶端的ID。下麵的代碼是CalculatorService的定義,需要注意的是我通過ServiceBehaviorAttribute將UseSynchronizationContext屬性設置成False,至於為什麼需要這麼做,是後續文章需要講述的內容。服務操作Add通過將當前線程掛起5秒鍾,用以模擬一個相對耗時的操作,便於我們更好的通過監控輸出的時間分析並發處理的情況。

   1: using System.ServiceModel;
   2: using System.Threading;
   3: using Artech.ConcurrentServiceInvocation.Service.Interface;
   4: namespace Artech.ConcurrentServiceInvocation.Service
   5: {
   6:     [ServiceBehavior(UseSynchronizationContext = false)]
   7:     public class CalculatorService : ICalculator
   8:     {
   9:         public double Add(double x, double y)
  10:         {
  11:             EventMonitor.Send(EventType.StartExecute);
  12:             Thread.Sleep(5000);
  13:             double result = x + y;           
  14:             EventMonitor.Send(EventType.EndExecute);
  15:             return result;
  16:         }
  17:     }
  18: }

四、通過Windows Forms應用寄宿服務

然後,我們在一個Windows Form應用中對上麵創建的CalculatorService進行寄宿,並將該應用作為服務端的監控器。在這個應用中,我隻添加了如圖1所示的簡單的窗體,整個窗體僅僅有一個唯一的ListBox控件,在運行的是時候相應的監控信息就實時地逐條追加到該ListBox之中。

image

圖1 服務端監控窗體設計界麵

我們通過注冊EventMonitor的靜態MonitoringNotificationSended事件的形式實時輸出服務端監控信息。同時,對CalculatorService的寄宿實現在監控窗體的Load事件中,整個窗體後台代碼如下所示。

   1: using System;
   2: using System.ServiceModel;
   3: using System.Threading;
   4: using System.Windows.Forms;
   5: using Artech.ConcurrentServiceInvocation.Service;
   6: using Artech.ConcurrentServiceInvocation.Service.Interface;
   7: namespace Artech.ConcurrentServiceInvocation.Hosting
   8: {
   9:     public partial class MonitorForm : Form
  10:     {
  11:         private SynchronizationContext _syncContext;
  12:         private ServiceHost _serviceHost;
  13:  
  14:         public MonitorForm()
  15:         {
  16:             InitializeComponent();
  17:         }
  18:  
  19:         private void MonitorForm_Load(object sender, EventArgs e)
  20:         {
  21:             string header = string.Format("{0, -13}{1, -22}{2}", "Client", "Time", "Event");
  22:             this.listBoxExecutionProgress.Items.Add(header);
  23:             _syncContext = SynchronizationContext.Current;
  24:             EventMonitor.MonitoringNotificationSended += ReceiveMonitoringNotification;
  25:             this.Disposed += delegate
  26:             {
  27:                 EventMonitor.MonitoringNotificationSended -= ReceiveMonitoringNotification;
  28:                 _serviceHost.Close();
  29:             };
  30:             _serviceHost = new ServiceHost(typeof(CalculatorService));
  31:             _serviceHost.Open();
  32:         }
  33:  
  34:         public void ReceiveMonitoringNotification(object sender, MonitorEventArgs args)
  35:         {    
  36:             string message = string.Format("{0, -15}{1, -20}{2}", args.ClientId, args.EventTime.ToLongTimeString(), args.EventType);
  37:             _syncContext.Post(state => this.listBoxExecutionProgress.Items.Add(message), null);
  38:         }
  39:     }
  40: }

下麵是WCF相關的配置,我們采用WS2007HttpBinding作為終結點的綁定類型。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service name="Artech.ConcurrentServiceInvocation.Service.CalculatorService">
   6:                 <endpoint address="https://127.0.0.1:3721/calculatorservice" binding="ws2007HttpBinding" contract="Artech.ConcurrentServiceInvocation.Service.Interface.ICalculator" />
   7:             </service>
   8:         </services>
   9:     </system.serviceModel>
  10: </configuration>

五、創建客戶端程序

最後我們編寫客戶端程序,這也是一個Windows Form應用。該應用既作為CalculatorService的客戶端程序而存在,同時也是客戶端的監控器。整個應用具有一個與圖1一樣的窗體。同樣以注冊EventMonitor的靜態MonitoringNotificationSended事件的形式實時輸出客戶端監控信息。在監控窗體的Load時間中,利用ThreadPool創建5個服務代理以並發的形式進行服務調用。這五個服務代理對象對應的客戶端ID分別為從1到5,並通過消息報頭的形式發送到服務端。整個監控窗體的代碼如下所示,相應的配置就不在列出來了。

   1: using System;
   2: using System.ServiceModel;
   3: using System.Threading;
   4: using System.Windows.Forms;
   5: using Artech.ConcurrentServiceInvocation.Service.Interface;
   6: namespace Artech.ConcurrentServiceInvocation.Client
   7: {
   8:     public partial class MonitorForm : Form
   9:     {
  10:         private SynchronizationContext _syncContext;
  11:         private ChannelFactory<ICalculator> _channelFactory;
  12:         private static int clientIdIndex = 0;
  13:  
  14:         public MonitorForm()
  15:         {
  16:             InitializeComponent();
  17:         }
  18:  
  19:         private void MonitorForm_Load(object sender, EventArgs e)
  20:         {
  21:             string header = string.Format("{0, -13}{1, -22}{2}", "Client", "Time", "Event");
  22:             this.listBoxExecutionProgress.Items.Add(header); 
  23:             _syncContext = SynchronizationContext.Current;
  24:             _channelFactory = new ChannelFactory<ICalculator>("calculatorservice");
  25:  
  26:             EventMonitor.MonitoringNotificationSended += ReceiveMonitoringNotification;
  27:             this.Disposed += delegate
  28:             {
  29:                 EventMonitor.MonitoringNotificationSended -= ReceiveMonitoringNotification;
  30:                 _channelFactory.Close();
  31:             };
  32:  
  33:             for (int i = 1; i <= 5; i++)
  34:             {
  35:                 ThreadPool.QueueUserWorkItem(state =>
  36:                 {
  37:                     int clientId = Interlocked.Increment(ref clientIdIndex);
  38:                     ICalculator proxy = _channelFactory.CreateChannel();
  39:                     using (proxy as IDisposable)
  40:                     {
  41:                         EventMonitor.Send(clientId, EventType.StartCall);
  42:                         using (OperationContextScope contextScope = new OperationContextScope(proxy as IContextChannel))
  43:                         {
  44:                             MessageHeader<int> messageHeader = new MessageHeader<int>(clientId);
  45:                             OperationContext.Current.OutgoingMessageHeaders.Add(messageHeader.GetUntypedHeader(EventMonitor.CientIdHeaderLocalName, EventMonitor.CientIdHeaderNamespace));
  46:                             proxy.Add(1, 2);
  47:                         }
  48:                         EventMonitor.Send(clientId, EventType.EndCall);
  49:                     }
  50:                 }, null);
  51:             }
  52:         } 
  53:  
  54:         public void ReceiveMonitoringNotification(object sender, MonitorEventArgs args)
  55:         {
  56:             string message = string.Format("{0, -15}{1, -20}{2}", args.ClientId, args.EventTime.ToLongTimeString(), args.EventType);
  57:             _syncContext.Post(state => this.listBoxExecutionProgress.Items.Add(message), null);
  58:         }
  59:     }
  60: }

到此為止,我們的監控程序就完成了。接下來我將借助於這麼一個監控程序對講述不同的實例上下文模式不同的並發模式、以及並發請求基於相同或者不同的代理的情況下,最終會表現出怎樣的並發處理行為。比如在ConcurrencyMode.Single + InstanceContextMode.Single的情況下,客戶端和服務端將會輸出如圖2所示的監控信息,從中我們會看出並發的請求最終卻是以串行化執行的。具體分析,請關注下篇。

image

圖2 ConcurrencyMode.Single + InstanceContextMode.Single條件下並發事件監控輸出



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

最後更新:2017-10-27 15:05:07

  上一篇:go  並發中的同步--WCF並發體係的同步機製實現
  下一篇:go  談談關於MVP模式中V-P交互問題