在《WCF 并发的本质》中,我们谈到了WCF提供的三种不同的并发模式,使开发者可以根据具体的情况选择不同的并发处理的策略。对于这三种并发模式,Multiple采用的并行的执行方式,而Single和Reentrant则是采用串行的执行方式。串行执行即同步执行,在WCF并发框架体系中,这样的同步机制是如何实现的呢?
一、Concurrency.Single模式下的同步实现
实际上,WCF并发框架体系下针对Concurrency.Single模式的实现非常简单,其本质就是对InstanceContext进行加锁。如果采用反编译工具查看InstanceContext的定义,你会发现InstanceContext类中定义了一个类型为System.Object名为ThisLock的内部属性,而该属性实际上就是对基类CommunicationObject同名属性的引用,相关代码入下所示。WCF就是通过对InstanceContext的ThisLock进行加锁,确保了对InstanceContext的同步访问。
1: public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
2: {
3: //其它成员
4: internal object ThisLock
5: {
6: get
7: {
8: return base.ThisLock;
9: }
10: }
11: }
12: public abstract class CommunicationObject : ICommunicationObject
13: {
14: //其它成员
15: protected object ThisLock { get; }
16: }
具体来讲,WCF服务端运行时在处理服务调用消息请求之后,利用实例上下文提供者(InstanceContextProvider)创建新的或者获取现有的InstanceContext。然后,WCF会将请求消息分发给该InstanceContext对消息进行进一步处理。在处理操作执行之前,如果发现相应的服务采用的并发模式是ConcurrencyMode.Single,WCF运行时会试图获取InstanceContext的ThisLock上的锁,或者说后续的操作进行再对InstanceContext的ThisLock锁定的情况下执行的。这样就保证了单一的InstanceContext对象在ConcurrencyMode.Single并发模式下永远是以同步的方式被调用的。
二、Concurrency.Reentrant模式下的同步实现
在ConcurrencyMode.Single并发模式下,从请求被WCF服务端运行时分发给相应的InstanceContext到请求处理完成的整个过程中,InstanceContext被锁定。如果在服务操作执行过程中涉及到对客户端的回调,并且回调操作采用请求/回复消息交换模式,当被WCF服务端运行时接收到从客户端返回的回复消息后,会将请求消息再次分发给相同的InstanceContext。运行时分发回调回复消息与普通服务调用请求消息采用相同的机制,同样需要在对InstanceContext成功锁定的情况下进行。很明显,这样产生了死锁(Deadlock)。
所以,如果在服务操作执行过程中需要对客户端实施回调,要么将采用单向(One-way)的方式进行回调,要么将服务的并发模式设置成ConcurrencyMode.Reentrant或者ConcurrencyMode.Multiple。否则,如图1所示的InvalidOperationException异常会在进行回调操作的时候抛出。从异常消息我们可以看出,VS的汉化真的不敢恭维,如果要正常理解异常消息的含义,你需要知道这里的“邮件”、“可重输入”和“多个”是依次对“Message”、“Reentrant”和“Multiple”的翻译。
图1 在Single模式执行回调导致的异常
如果我们真的需要在服务操作过程中实施基于请求/回复模式的回调,毫无疑问采用Concurrency.Multiple并发模式可以解决死锁的问题,因为Concurrency.Multiple模式根本就是存在对InstanceContext加锁的问题。那么,在Concurrency.Reentrant模式下,WCF并发框架体系又是如何解决这个问题的呢?
Reentrant,翻译成汉语就是“重入”(VS将其翻译成“重输入”简直莫名其妙),意思是服务操作过程中完成了对外调用(Call Out)还能重新回到相应的位置继续执行。同Concurrency.Single模式一样,WCF运行时将调用请求消息分发给相应的InstanceContext之前,会先对其加锁。但是,在开始实施回调的之前,对InstanceContext的锁定会被解除,当回调返回后再对其加锁。
对于Concurrency.Reentrant有一点需要特别说明,当服务端进行回调时,由于加载InstanceContext上的锁会被释放,意味着其它服务请求会被分发给该InstanceContext。当回调返回的时候,如果InstanceContext正被用于才处理在进行回调过程抵达的请求,虽然自己是先来者,依然会等待,因为重入后的InstanceContext被锁定。如果等待的时间超过设定的超时时限,客户端会抛出TimeoutException异常。
由于WCF的并发是针对某个封装了服务实例的InstanceContext而言的,所以在不同的实例上下文模式下,会表现出不同的并发行为。在下一篇文章中,我将从具体的实例上下文模式的角度来剖析WCF的并发,敬请期待。
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。