在[第1篇]中,我们介绍了WCF关于实例管理一些基本的知识点,包括InstanceContext、InstanceContextMode、已经如何通过ServiceBehaviorAttribute应用不同的实例上下文模式给不同的服务。在[第1篇]中,对WCF采用的三种不同实例上下文模式进行了简单的比较,本篇的重点方法对单调(PerCall)模式为进行详细介绍。
在单调(Per-Call)实例上下文模式下,WCF总是创建一个新的服务实例上下文处理接收到的每一个服务调用请求,并在服务操作执行结束后,回收服务上下文和服务实例。换句话说,单调服务实例上下文模式使服务实例上下文的生命周期与服务调用本身绑定。我们首先来介绍单调模式下服务实例上下文具体有怎样的生命周期。
一、 单调模式下的服务实例上下文提供机制
对于单调模式,服务实例的生命周期大体上可以看成服务操作执行的生命周期。服务实例在服务操作执行前被创建,在操作完成之后被回收。下面的列表揭示了在单调模式下,对于每一次服务调用请求,WCF的整个服务实例激活过程:
- WCF服务端接收到来自客户端的服务调用请求;
- 通过实例上下文提供者(InstanceContextProvider)对象试图获取现有服务实例的实例上下文,对于单调模式,返回的实例上下文永远为空;
- 如果获取实例上下文为空,则通过实例提供者(IntanceProvider)创建服务实例,封装到新创建的实例上下文中;
- 通过InstanceContext的GetServiceInstance方法获取服务实例对象,借助操作选择器(OperationSelector)选择出相应的服务操作,最后通过操作执行器(OperationInvoker)对象执行相应的操作方法;
- 操作方法执行完毕后,关闭被卸载InstanceContext对象。在此过程中,会调用InstanceProvider对象释放服务实例,如果服务类型实现了接口IDisposable,则会调用Disposable方法;
- 服务实例成为垃圾对象,等待GC回收。
对于上述列表中提到的InstanceContextProvider、InstanceProvider等重要的对象,以及相关的实现机制,将在本系列后续的部分进行单独讲解。为了加深读者的理解,这里通过一个简单的例子来演示在单调模式下服务实例的整个激活流程。
二、 实例演示:单调模式下服务实例的生命周期
本案例依然沿用典型的4层结构和计算服务的场景,下面是服务契约和具体服务实现的定义。在CalculatorService类型上,通过ServiceBehaviorAttribute特性将实例上下文模式设为单调(Per-Call)模式。为了演示服务实例的创建、释放和回收,我们分别定义了无参构造函数,终止化器(Finalizer)以及实现的接口IDisposable,并在所有的方法中输出相应的指示性文字,以便更容易地观测到它们执行的先后顺序。
1: using System.ServiceModel;<!--CRLF-->
2: namespace Artech.WcfServices.Contracts<!--CRLF-->
3: {
<!--CRLF-->
4: [ServiceContract(Namespace="http://www.artech.com/")]<!--CRLF-->
5: public interface ICalculator<!--CRLF-->
6: {
<!--CRLF-->
7: [OperationContract]
<!--CRLF-->
8: double Add(double x, double y);<!--CRLF-->
9: }
<!--CRLF-->
10: }
<!--CRLF-->
1: using System;<!--CRLF-->
2: using System.ServiceModel;<!--CRLF-->
3: using Artech.WcfServices.Contracts;<!--CRLF-->
4: namespace Artech.WcfServices.Services<!--CRLF-->
5: {
<!--CRLF-->
6: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
<!--CRLF-->
7: public class CalculatorService : ICalculator, IDisposable<!--CRLF-->
8: {
<!--CRLF-->
9: public CalculatorService()<!--CRLF-->
10: {
<!--CRLF-->
11: Console.WriteLine("Service object is instantiated.");<!--CRLF-->
12: }
<!--CRLF-->
13: ~CalculatorService()
<!--CRLF-->
14: {
<!--CRLF-->
15: Console.WriteLine("Service object is finalized.");<!--CRLF-->
16: }
<!--CRLF-->
17:
<!--CRLF-->
18: public void Dispose()<!--CRLF-->
19: {
<!--CRLF-->
20: Console.WriteLine("Service object is disposed.");<!--CRLF-->
21: }
<!--CRLF-->
22: public double Add(double x, double y)<!--CRLF-->
23: {
<!--CRLF-->
24: Console.WriteLine("Operation method is invoked.");<!--CRLF-->
25: return x + y;<!--CRLF-->
26: }
<!--CRLF-->
27: }
<!--CRLF-->
28: }
<!--CRLF-->
为了演示GC对服务实例的回收,在进行服务寄宿的时候,通过System.Threading.Timer使GC每隔10毫秒强制执行一次垃圾回收。
1: using System;<!--CRLF-->
2: using System.ServiceModel;<!--CRLF-->
3: using System.Threading;<!--CRLF-->
4: using Artech.WcfServices.Services;<!--CRLF-->
5: namespace Artech.WcfServices.Hosting<!--CRLF-->
6: {
<!--CRLF-->
7: public class Program<!--CRLF-->
8: {
<!--CRLF-->
9: private static Timer GCScheduler;<!--CRLF-->
10:
<!--CRLF-->
11: static void Main(string[] args)<!--CRLF-->
12: {
<!--CRLF-->
13: GCScheduler = new Timer(<!--CRLF-->
14: delegate<!--CRLF-->
15: {
<!--CRLF-->
16: GC.Collect();
<!--CRLF-->
17: }, null, 0, 100);<!--CRLF-->
18: using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))<!--CRLF-->
19: {
<!--CRLF-->
20: serviceHost.Open();
<!--CRLF-->
21: Console.Read();
<!--CRLF-->
22: }
<!--CRLF-->
23: }
<!--CRLF-->
24: }
<!--CRLF-->
25: }
<!--CRLF-->
通过一个控制台应用程序对服务进行成功寄宿后,客户端通过下面的代码,使用相同的服务代理对象进行两次服务调用。
1: using System;<!--CRLF-->
2: using System.ServiceModel;<!--CRLF-->
3: using Artech.WcfServices.Contracts;<!--CRLF-->
4: namespace Artech.WcfServices.Clients<!--CRLF-->
5: {
<!--CRLF-->
6: class Program<!--CRLF-->
7: {
<!--CRLF-->
8: static void Main(string[] args)<!--CRLF-->
9: {
<!--CRLF-->
10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))<!--CRLF-->
11: {
<!--CRLF-->
12: ICalculator calculator = channelFactory.CreateChannel();
<!--CRLF-->
13: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));<!--CRLF-->
14: Console.WriteLine("x + y = {2} when x = {0} and y = {1}: {3}", 1, 2, calculator.Add(1, 2));<!--CRLF-->
15: }
<!--CRLF-->
16: }
<!--CRLF-->
17: }
<!--CRLF-->
18: }
<!--CRLF-->
从运行后服务端的输出可以看出,对于两次服务调用请求,服务端先后创建了两个服务实例,在操作方法成功执行后,Dispose方法得以执行。而终止化器(Finalizer)是被GC在后台执行的,所以执行的时机不能确定。不过有一点可以从中得到证实:当服务操作执行时,服务实例变成了“垃圾”对象,并可以被GC回收以腾出占据的内存空间。
Service object is instantiated.<!--CRLF-->
Operation method is invoked.<!--CRLF-->
Service object is disposed.<!--CRLF-->
Service object is instantiated.<!--CRLF-->
Operation method is invoked.<!--CRLF-->
Service object is disposed.<!--CRLF-->
Service object is finalized.<!--CRLF-->
Service object is finalized.<!--CRLF-->
三、 服务实例上下文的释放
如果服务实例须要引用一些非托管资源,比如数据库连接、文件句柄等,须要及时将其释放。在这种情况下,我们可以通过实现IDisposable接口,在Dispose方法中进行相应的资源回收工作。在单调实例上下文模式下,当服务操作执行时,Dispose方法会自动被执行,这一点已经通过上面的案例演示得到证实。
对于实现了IDisposable接口的Dispose方法,有一点值得注意的是:该方法是以与操作方法同步形式执行的。也就是说,服务操作和Dispose方法在相同的线程中执行。认识这一点很重要,因为无论采用怎样的实例模式,在支持会话(Session)的情况下如果服务请求来自于同一个服务代理,服务操作都会在一个线程下执行。对于单调模式就会出现这样的问题:由于Dispose方法同步执行的特性,如果该方法是一个比较耗时的操作,那么来自于同一个服务代理的服务后续调用请求将不能得到及时执行。WCF只能在上一个服务实例被成功释放之后,才能处理来自相同服务代理的下一个服务调用请求。为了让读者体会到同步方式释放服务实例在应用中的影响,并证明同步释放服务实例的现象,我们对上面的案例略加改动。
在CalculatorService中,通过线程休眠的方式模拟耗时的服务实例释放操作(5秒)。在Dispose和Add方法中,除了输出具体操作名称之外,还会输出当前的线程ID和执行的开始时间,代码如下所示。
1: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
<!--CRLF-->
2: public class CalculatorService : ICalculator, IDisposable<!--CRLF-->
3: {
<!--CRLF-->
4: public void Dispose()<!--CRLF-->
5: {
<!--CRLF-->
6: Console.WriteLine("Time: {0}; Thread ID: {1}; Service object is disposed.", DateTime.Now, Thread.CurrentThread.ManagedThreadId);<!--CRLF-->
7: Thread.Sleep(5000);
<!--CRLF-->
8: }
<!--CRLF-->
9: public double Add(double x, double y)<!--CRLF-->
10: {
<!--CRLF-->
11: Console.WriteLine("Time: {0}; Thread ID: {1}; Operation method is invoked.", DateTime.Now, Thread.CurrentThread.ManagedThreadId);<!--CRLF-->
12: return x + y;<!--CRLF-->
13: }
<!--CRLF-->