• 实践重于理论——创建一个监控程序探测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之中。

    image

    图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所示的监控信息,从中我们会看出并发的请求最终却是以串行化执行的。具体分析,请关注下篇。

    image

    图2 ConcurrencyMode.Single + InstanceContextMode.Single条件下并发事件监控输出

    作者:Artech
    出处:http://artech.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    [安装程序配置服务器失败]解决SQL Server2000安装失败
    C# 操作 XML 增 删 改 查
    批量修改文件的编码格式
    获取SqlServer2005表结构(字段,主键,外键,递增,描述)
    .NET 特性Attribute[一]
    windows2003远程桌面退出后系统自动注销的解决方法
    SqlServer 无日志文件附加
    接受来自服务器的数据连接时发生超时(30000 毫秒)问题原因及解决方法
    .net中数据集合导出为Excel(支持泛型及显示字段顺序,可自定义显示列名)
    EF中自编写SQL脚本查询结果(适用于复杂SQL逻辑提高查询效率)
  • 原文地址:https://www.cnblogs.com/artech/p/1691862.html
Copyright © 2020-2023  润新知