• [织梦吧]唯一域名:www.dedecms8.com,织梦DedeCMS学习平台.

当前位置: > 编程与数据库 > net编程 >

实践重于理论 - 创建一个监控程序探测WCF的并发处理机制

来源: www.dedecms8.com 编辑:织梦吧 时间:2012-06-28点击:

为了使读者对采用不同实例上下文对并发的影响有一个深刻的认识,会创建一个简单的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: </configur五、创建客户端程序

最后我们编写客户端程序,这也是一个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条件下并发事件监控输出ation>

About D8

  • ©2014 织梦吧(d8) DedeCMS学习交流平台
  • 唯一网址 www.DedeCMS8.com 网站地图
  • 联系我们 1170734538@qq.com ,  QQ