服务调用的目的体现在对某项服务功能的消费上,而功能的实现又定义在相应的服务类型中。不论WCF服务端框架处理服务调用请求的流程有多么复杂,最 终都落实在服务实例的激活和操作方法的执行上面。WCF中的实例管理(Instance Management)旨在解决服务实例的激活和服务实例生命周期的控制。
会话(Session)的目的在于保持来自相同客户端(服务 代理)多次服务调用之间的状态。从消息交换的角度来讲,会话通过消息识别机制判断调用某个服务的消息来源,从而将来自相同客户端的所有消息关联在一起。所 以,会话实现了消息关联(Message Correlation)。
实例与会话是WCF非常重要的两个特性,它们既相对独立,又互相 制约。实例模式与对会话支持程度的不同组合,会让最终的服务表现出截然不同的行为。对实例管理和会话的合理利用,对于改善和提高WCF服务应用的可扩展性 (Scalability)、性能(Performance)、吞吐量(Throughput)等具有决定性作用。服务实例对象并不是孤立存在的,而是被 封装到一个特殊实例上下文(InstanceContext)对象之中,本系列文章从实例上下文说起。
一、 实例上下文(InstanceContext)
实例上下文是对服务实例的封装,是WCF管理服务实例 生命周期的依托。我们先撇开WCF,来简单介绍一下在托管的环境中,公共语言运行时(CLR)是如何进行托管对象的生命周期的。在一个托管应用程序中,我 们通过不同的方式创建一个托管对象(比如通过new关键字、反射或反序列化等)时,CLR会在托管堆为该对象开辟一块内存空间。对象的本质就是存储于某块 内存中数据的体现,对象的生命周期终止于相应内存被回收之时。对于CLR来说,负责对托管堆(在这里主要指GC堆)进行回收的组件是垃圾收集器 (GC),GC掌握着托管对象的生杀大权,决定着托管对象的生命周期。
当GC在进行垃圾回收的时候,会将“无用”的对象标记为垃圾对 象,然后再对垃圾对象进行清理。GC对“无用”对象的识别机制很简单:判断对象是否被“根(Root)”所引用。在这里,“根”是对一组当前正被使用,或 者以后可能被使用的对象的统称,大体包括这样的对象:类型的静态字段或当前的方法参数和局部变量、CPU寄存器等。
所以,孤立存在的对 象将难逃被GC回收的厄运。反之,如果希望某个对象常驻内存中,我们唯一的方式就是通过某个“根”引用该对象。本章所讲的实例管理,就是对服务实例生命周 期的管理,即让服务实例按照我们希望的方式创建、存活和消亡,所以我们唯一的方式也只能是:在希望服务实例存活的时候让它被某个“根”引用,从而阻止GC 将其回收;在希望服务实例被回收的时候连“根”去除,使GC能够将其回收。而本节所讲的实例上下文(InstanceContext)就扮演着“根”的角 色。
说到实例上下文,相信读者不会感到陌生,因为在进行WCF双向(Duplex)通信的时候,我们通过实例上下文来封装回调对象。在 WCF中,实例上下文不仅仅用于对回调对象的封装,也用于对真正服务实例的封装。实际上可以将WCF的双向通信理解成一种对等通信,通信的双方是对等的参 与者,并没有严格的服务端和客户端之分,或者说通信的双方交替地扮演着服务与客户的角色。客户端正常调用服务端操作是一种服务调用;服务端回调客户端操作 也可以看成是一种服务调用。因此,通过实例上下文对回调对象和服务实例进行封装本质上是一致的。
实例上下文对服务实例的封装大体可以通 过图1 表示。一个WCF服务通过一个ServiceHost进行寄宿,并添加一到多个终结点。对于接收到的服务调用请 求,如果相应的实例上下文存在,则通过它得到服务实例来处理服务请求,否则创建服务实例并通过实例上下文对其进行封装,然后再通过实例上下文得到具体的服 务实例进行服务请求处理。
图 1 实例上下文对服务实例的封装
实例上下文通过类型 System.ServiceModel.InstanceContext表示。InstanceContext继承自 CommunicationObject,实现了IExtensibleObject<InstanceContext>接口。 InstanceContext的定义如下面的代码所示:
1: public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
2:
{
3: //其他成员
4: public InstanceContext(object implementation);
5: public InstanceContext(ServiceHostBase host);
6: public InstanceContext(ServiceHostBase host, object implementation);
7:
8: public object GetServiceInstance();
9: public object GetServiceInstance(Message message);
10: public void ReleaseServiceInstance();
11:
12: public IExtensionCollection<InstanceContext> Extensions { get; }
13: public ServiceHostBase Host { get; }
14: public ICollection<IChannel> IncomingChannels { get; }
15: public ICollection<IChannel> OutgoingChannels { get; }
16: public SynchronizationContext SynchronizationContext { get; set; }
17:
}
InstanceContext具有三个构造函数,接受ServiceHostBase对象和具体的实例对象作为其输入参数。 GetServiceInstance和ReleaseServiceInstance用户服务实例的获取和释放。IncomingChannels和 OutgoingChannels则表示入栈和出栈信道集合。而通过SynchronizationContext属性则可以设置或获取用于异步操作的同 步上下文,比如服务操作须要在非UI线程下操作一个Windows Form的控件,你就需要基于UI线程的同步上下文(SynchronizationContext)。
二、实例上下文模式(InstanceContext Mode)
实例上下文模式(IntanceContext Mode)表示服务端的服务实例与客户端的服务代理的绑定方式。如果读者熟悉.NET Remoting,肯定会很清楚.NET Remoting具有两种不同的远程对象激活方式:服务端激活对象(SAO:Server Activated Object)和客户端激活对象(CAO:Client Activated Object),而前者又具有两种不同的变体:单调(SingleCall)和单例(Singleton)。单调模式意味着服务端对于接收到的调用,都会 创建新的远程对象,而单例模式则表示服务端使用相同的远程对象处理来自不同客户端的所有远程调用。单调和单例模式体现了两种极端的远程对象激活方式,而 CAO则是一种相对折中的方式:一个客户端代理对象与一个远程对象一一匹配。WCF实例上下文模式与.NET Remoting的远程对象激活方式类似,同样具有三种不同的实例上下文模式,分别与上述三种激活方式匹配。这三种实例上下文模式分别是:单调(Per- Call)模式、会话(Per-Session)模式和单例(Single)模式。
1、单调(Per-Call)模式
单调模式相当于.NET Remoting的SingleCall远程对象激活方式。如果采用单调实例上下文模式,对于每一个服务调用,不论是来自相同的客户端(服务代理)还是不 同的客户端,WCF总是创建一个全新的服务实例和实例上下文对象来处理服务调用请求。在服务操作执行完毕,实例上下文对象和被封装的服务实例被回收调。图 2揭示了在单调模式下实例上下文、服务实例和服务代理之间的关联。
图2 单调模式下服务代理与服务实例上下文之间的关联
2、会话(Per-Session)模式
会话(Session)的目的在于保持来自相同客户端(即同一个服务代理)多次服务调用之间的状态。如果从消息交互的角度来讲,通过会话可以将来自 相同客户端的多个消息关联在一起。在会话实例上下文模式下,WCF为每一个服务代理对象分配一个单独的服务实例上下文对象,对于来自相同服务代理的所有服 务调用请求,都将分发给相同的服务实例上下文处理。会话模式与.NET Remoting下的CAO远程对象激活模式类似,图3揭示了会话模式下实例上下文、服务实例和服务代理之间的关系。
图3 会话模式下服务代理与服务实例上下文之间的关联
3、单例(Single)模式
单例模式意味着WCF为每个服务维护一个并且仅维护一个服务实例上下文。不论请求来自相同的服务代理还是不同的服务代理,处理服务调用请求都是同一 个服务实例上下文对象。单例模式相当于.NET Remoting下的Singleton远程对象激活方式,图4揭示了单例模式下实例上下文、服务实例和服务代理之间的关系。
图4 会话模式下服务代理与服务实例上下文之间的关联
三、 实例服务行为
在介绍服务寄宿的时候,我们谈到过WCF下“契约(Contract)”和“行为(Behavior)”的区别:契约是涉及双边的描述(契约是服务 的提供者和服务消费者进行交互的手段),那么行为就是基于单边的描述。客户端行为体现的是WCF如何进行服务调用的方式,而服务端行为则体现了WCF的请 求分发方式。所以服务契约会通过元数据对外发布,而服务行为则对于客户端是透明的。
对于客户端来讲,它所关心的是通过服务调用能够获得正确的结果,而不会关心服务端采用怎样的模式来激活服务实例。所以,WCF实例管理通过服务行为 体现,不同的实例上下文模式通过ServiceBehaviorAttribute特性指定。在ServiceBehaviorAttribute中,通 过设置InstanceContextMode属性来指定不同的服务实例上下文模式。
1:
[AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3:
{
4: //其他成员
5: public InstanceContextMode InstanceContextMode { get; set; }
6:
}
属性InstanceContextMode的类型为System.ServiceModel.InstanceContextMode枚举,三个 枚举值PerCall、PerSession和Single分别表示上述的三种实例上下文模式。默认选项为PerSession。
1: public enum InstanceContextMode
2:
{
3:
PerCall,
4:
PerSession,
5:
Single
6:
}
在本系列后续部分,我将对每一种实例模式的实现原理进行逐个剖析,相信极大的加深读者对WCF下的服务对象生命周期管理机制的理解。