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

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

原文链接

时间: 2024-10-05 16:27:56

实践重于理论——创建一个监控程序探测WCF的并发处理机制的相关文章

实践重于理论

为了使读者对采用不同实例上下文对并发的影响有一个深刻的认识,会创建一个简单的WCF应用,并在此基础上添加监控功能,主要监控各种事件的执行时间,比如客户端服务调用的开始和结束时间,服务操作开始执行和结束执行的时间等等.读者可以根据实时输出的监控信息,对WCF的并发处理情况有一个很直观的认识. [源代码从这里下载] 一.服务契约定义 本实例依然采用我们熟悉的四层结构,即契约.服务.寄宿和客户端.为了以可视化的形式实时输出监控信息,对于客户端和服务寄宿程序均采用Windows Form应用类型.我们依

WCF后续之旅(13) 创建一个简单的WCF SOAP Message拦截、转发工具

WCF是.NET平台下实现SOA的一种手段,SOA的一个重要的特征就基于Message的通信方式.从Messaging的角度讲,WCF可以看成是对Message进行发送.传递.接收.基础的工具.对于一个消息交换的过程,很多人只会关注message的最初的发送端和最终的接收端.实际上在很多情况下,在两者之间还存在很多的中间结点(Intermediary),这些中间结点在可能在实际的应用中发挥中重要的作用.比如,我们可以创建路由器(Router)进行消息的转发,甚至是Load Balance:可以创

WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]

WCF是.NET平台下实现SOA的一种手段,SOA的一个重要的特征就基于Message的通信方式.从Messaging的角度讲,WCF可以看成是对Message进行发送.传递.接收.基础的工具.对于一个消息交换的过程,很多人只会关注message的最初的发送端和最终的接收端.实际上在很多情况下,在两者之间还存在很多的中间结点(Intermediary),这些中间结点在可能在实际的应用中发挥中重要的作用.比如,我们可以创建路由器(Router)进行消息的转发,甚至是Load Balance:可以创

我的WCF之旅(1):创建一个简单的WCF程序

写在前面 在Microsoft提出.NET战略以来, 先后推出了一系列产品和技术, 这些产品和技术为我们在.NET平台下建立企业级的分布式应用提供了很大的 便利.这些技术和产品包括:.NET Remoting,XML WebSerivce,WSE(2.0,3.0),Enterprise Service, MSMQ ...... 我们知道,和一个相对独立的应用不同,我们开发一个分布式应用, 尤其是开发一个企业级的分布式应用, 我们需要考虑较多的东西.比如我们要考虑数据在不同的应用之间传递时采取什么

WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]

在Part I 中,我们创建了一个InterceptService,并且通过一个特殊的EndpointBehavior,ClientViaBehavior实现了message的拦截.转发功能.在本节中,我们将讨论另外一种不同的实现方式.如何说ClientViaBehavior是基于Client端的实现方式,那么我们今天讨论的是基于Service的实现方式. 在对新的实现方式展开介绍之前,我们先来介绍一下关于逻辑地址和物理地址. 一.逻辑地址和物理地址 我们知道,WCF通过Endpoint进行通信

FBI: 给我开个后门!苹果:我不能创建一个有监控性质的 iOS!

编者按:本文作者 Joseph Steinberg 是 SecureMySocial 的 CEO,针对苹果与 FBI 之争,他发表了自己的看法,并提及这件事对于广大民众的切身利益有和影响. 苹果与 FBI 的争端来源于去年一次枪战.2015年12月,两名恐怖分子在加州圣贝纳迪诺市发起袭击,造成 14 人死亡,22 人受伤,两人在枪战中被警方击毙.案发中,警方在恐怖分子 Syed Farook 的汽车上发现一部 iPhone 5C 手机.执法机构想利用手机上的数据协助判定在案发期间是否有他人参与一

怎样创建一个Xcode插件(part 1)

原文:How To Create an Xcode Plugin: Part 1/3 原作者:Derek Selander 译者:@yohunl 译者注:原文使用的是xcode6.3.2,我翻译的时候,使用的是xcode7.2.1,经过验证,文章中说说的依然是有效的.在文中你可以学习到一系列的技能,非常值得一看. 苹果的"一个足以应付所有"策略使得它的产品越来越像一个难以下咽的药丸.尽管苹果已经将一些工作流带给了iOS/OS X的开发者,我们仍然希望通过插件来使得Xcode更加顺手!

线程执行者(二)创建一个线程执行者

创建一个线程执行者 使用Executor framework的第一步就是创建一个ThreadPoolExecutor类的对象.你可以使用这个类提供的4个构造器或Executors工厂类来 创建ThreadPoolExecutor.一旦有执行者,你就可以提交Runnable或Callable对象给执行者来执行. 在这个指南中,你将会学习如何使用这两种操作来实现一个web服务器的示例,这个web服务器用来处理各种客户端请求. 准备工作 你应该事先阅读第1章中创建和运行线程的指南,了解Java中线程创

为打印输出创建一个CSS样式

css|创建|打印 有的时候,你可以不必在你的Web站点上创建一个独立的与打印相关的页面. 大多数的Web页面都显示在电脑屏幕上,人们通过屏幕观看,但是,有时候用户想要将一些网上的内容打印出来,这就需要与打印媒体连接起来.由于原来采用的是能让Web页面在屏幕上显示的效果很好的那种格式,但在打印时效果并不那么好,Web创建者们通常都会创建一些独立的页面,这些页面与打印机联系紧密,而用户也需要经常打印这些页面.但如果你使用了XHTML标记和CSS,你就没必要再创建一个独立的与打印机相连接的页面了,你