程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 實踐重於理論 - 創建一個監控程序探測WCF的並發處理機制

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

編輯:關於.NET

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

一、服務契約定義

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

1: using System.ServiceModel;
   2: namespace Artech.ConcurrentServiceInvocation.Service.Interface
   3: {
   4:   [ServiceContract(Namespace="http://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 = "http://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之中。

圖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="http://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所示的監控信息,從中我們會看出並發的請求最終卻是以串行化執行的。具體分析,請關注下篇。

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved