由于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条件下并发事件监控输出
作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。