由于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="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条件下并发事件监控输出
 
                    
                 
				  
 
        

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号