為了使讀者對采用不同實例上下文對並發的影響有一個深刻的認識,會創建一個簡單的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條件下並發事件監控輸出