在[第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;
   2: namespace Artech.WcfServices.Contracts
   3: {
   4:     [ServiceContract(Namespace="http://www.artech.com/")]
   5:     public interface ICalculator
   6:     {
   7:         [OperationContract]
   8:         double Add(double x, double y);
   9:     }
  10: }

   1: using System;
   2: using System.ServiceModel;
   3: using Artech.WcfServices.Contracts;
   4: namespace Artech.WcfServices.Services
   5: {
   6:     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
   7:     public class CalculatorService : ICalculator, IDisposable
   8:     {
   9:         public CalculatorService()
  10:         {
  11:             Console.WriteLine("Service object is instantiated.");
  12:         }
  13:         ~CalculatorService()
  14:         {
  15:             Console.WriteLine("Service object is finalized.");
  16:         }
  17:  
  18:         public void Dispose()
  19:         {
  20:             Console.WriteLine("Service object is disposed.");
  21:         }
  22:         public double Add(double x, double y)
  23:         {
  24:             Console.WriteLine("Operation method is invoked.");
  25:             return x + y;
  26:         }
  27:     }
  28: }

为了演示GC对服务实例的回收,在进行服务寄宿的时候,通过System.Threading.Timer使GC每隔10毫秒强制执行一次垃圾回收。

   1: using System;
   2: using System.ServiceModel;
   3: using System.Threading;
   4: using Artech.WcfServices.Services;
   5: namespace Artech.WcfServices.Hosting
   6: {
   7:     public class Program
   8:     {
   9:         private static Timer GCScheduler;
  10:  
  11:         static void Main(string[] args)
  12:         {
  13:             GCScheduler = new Timer(
  14:                 delegate
  15:                 {
  16:                     GC.Collect();
  17:                 }, null, 0, 100);
  18:             using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
  19:             {               
  20:                 serviceHost.Open();                
  21:                 Console.Read();
  22:             }
  23:         }
  24:     }
  25: }

通过一个控制台应用程序对服务进行成功寄宿后,客户端通过下面的代码,使用相同的服务代理对象进行两次服务调用。

   1: using System;
   2: using System.ServiceModel;
   3: using Artech.WcfServices.Contracts;
   4: namespace Artech.WcfServices.Clients
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
  11:             {
  12:                 ICalculator calculator = channelFactory.CreateChannel();
  13:                 Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
  14:                 Console.WriteLine("x + y = {2} when x = {0} and y = {1}: {3}", 1, 2, calculator.Add(1, 2));
  15:             }
  16:         }
  17:     }
  18: }

从运行后服务端的输出可以看出,对于两次服务调用请求,服务端先后创建了两个服务实例,在操作方法成功执行后,Dispose方法得以执行。而终止化器(Finalizer)是被GC在后台执行的,所以执行的时机不能确定。不过有一点可以从中得到证实:当服务操作执行时,服务实例变成了“垃圾”对象,并可以被GC回收以腾出占据的内存空间。

Service object is instantiated.
Operation method is invoked.
Service object is disposed.
Service object is instantiated.
Operation method is invoked.
Service object is disposed.
Service object is finalized.
Service object is finalized.

三、 服务实例上下文的释放

如果服务实例须要引用一些非托管资源,比如数据库连接、文件句柄等,须要及时将其释放。在这种情况下,我们可以通过实现IDisposable接口,在Dispose方法中进行相应的资源回收工作。在单调实例上下文模式下,当服务操作执行时,Dispose方法会自动被执行,这一点已经通过上面的案例演示得到证实。

对于实现了IDisposable接口的Dispose方法,有一点值得注意的是:该方法是以与操作方法同步形式执行的。也就是说,服务操作和Dispose方法在相同的线程中执行。认识这一点很重要,因为无论采用怎样的实例模式,在支持会话(Session)的情况下如果服务请求来自于同一个服务代理,服务操作都会在一个线程下执行。对于单调模式就会出现这样的问题:由于Dispose方法同步执行的特性,如果该方法是一个比较耗时的操作,那么来自于同一个服务代理的服务后续调用请求将不能得到及时执行。WCF只能在上一个服务实例被成功释放之后,才能处理来自相同服务代理的下一个服务调用请求。为了让读者体会到同步方式释放服务实例在应用中的影响,并证明同步释放服务实例的现象,我们对上面的案例略加改动。

在CalculatorService中,通过线程休眠的方式模拟耗时的服务实例释放操作(5秒)。在Dispose和Add方法中,除了输出具体操作名称之外,还会输出当前的线程ID和执行的开始时间,代码如下所示。

   1: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
   2: public class CalculatorService : ICalculator, IDisposable
   3: {
   4:     public void Dispose()
   5:     {
   6:         Console.WriteLine("Time: {0}; Thread ID: {1}; Service object is disposed.", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
   7:         Thread.Sleep(5000);
   8:     }
   9:     public double Add(double x, double y)
  10:     {
  11:         Console.WriteLine("Time: {0}; Thread ID: {1}; Operation method is invoked.", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
  12:         return x + y;
  13:     }
  14: }

在客户端,我们创建两个不同的服务代理,通过ThreadPool分别对它们进行2次异步调用。下面是相关的服务调用代码。

   1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
   2: {
   3:     ICalculator calculator = channelFactory.CreateChannel();
   4:     ThreadPool.QueueUserWorkItem(delegate
   5:     {
   6:         Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), DateTime.Now);
   7:     });
   8:     ThreadPool.QueueUserWorkItem(delegate
   9:     {
  10:         Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), DateTime.Now);
  11:     });
  12:     Console.Read();    
  13: } 

从客户端和服务端输出结果的比较,我们可以清晰地看出基于相同服务代理的操作方法和Dispose方法都执行在相同的线程下(线程ID为12),并且两次服务操作的间隔为服务实例释放的时间:5秒。由于服务操作和Dispose方法的同步执行,导致服务端忙于释放上一个服务实例,而不能及时处理来自相同服务代理的下一个服务调用请求。

客户端:

3/6/2009 7:12:34 PM: x + y = 3 when x = 1 and y = 2
3/6/2009 7:12:39 PM: x + y = 3 when x = 1 and y = 2

服务端:

Time: 3/6/2009 7:12:34 PM; Thread ID: 12; Operation method is invoked.
Time: 3/6/2009 7:12:34 PM; Thread ID: 12; Service object is disposed.
Time: 3/6/2009 7:12:39 PM; Thread ID: 12; Operation method is invoked.
Time: 3/6/2009 7:12:39 PM; Thread ID: 12; Service object is disposed.

关于服务实例的同步执行机制,还有一点需要说明是,在Dispose方法中,可以得到当前OperationContext,而OperationContext在会话(Per-Session)实例上下文模式下是不可得的。

四、单调模式与可扩展性

在单调模式下,如果不考虑GC对垃圾对象回收的滞后性,服务实例的数量可以看成是当前正在处理的服务调用请求的数量。相关的资源能够在服务操作执行完毕之后得到及时回收(通过实现IDisposable接口,将资源回收操作实现在Dispose方法中)。所以,单调模式具有的优势是能够最大限度地发挥资源的利用效率,避免了资源的闲置和相互争用。

这里的资源不仅仅包括服务实例本事占据的内存资源,也包括服务实例直接或间接引用的资源。由于单调模式采用基于服务调用的服务实例激活和资源分配方式,所以服务实例或被分配的资源自始至终都处于“工作”状态,不会造成资源的闲置。服务实例在完成其使命之后,能够对资源进行及时的释放,被释放的资源可以及时用于对其他服务请求的处理。

我们将单调模式和后面要讲的会话模式作一个对比,后者采用基于服务代理的实例激活和生命周期管理。也就是说,在不考虑WCF闲置请求策略(当服务实例在超出某个时间段没有被使用的情况下,WCF将其清理)的情况下,服务实例的生命始于通过服务实例进行第一次服务调用,或者调用Open方法开启服务代理之时,服务代理的关闭会通知WCF服务端框架将对应的服务实例进行释放。举一个极端的例子,服务实例在存续期间需要引用一个非托管资源,比如是数据库连接,假设最大允许的并发连接为100。现在,先后100个客户端(或者服务代理)进行服务调用请求,毫无疑问,100个服务实例会被创建并同时存在于服务端的内存之中,并且每一个服务实例引用一个开启状态的数据库连接,那么当来自第101个客户端服务调用请求抵达时,将得不到处理,除非在它的超时时限到达之前,有一个客户端自动将服务代理关闭。

但是,对于相同的场景,如果采用单调的模式,就能应付自如,因为在每次服务调用之后,数据库的连接可以及时地得到关闭和释放。

对于单调模式,很多读者一开始就会心存这样的疑问:服务实例的频繁创建,对性能不会造成影响吗?在前一章中,我们就说过:高性能(Performance)和高可扩展性(Scalability)是软件设计与架构中永远不可以同时兼顾的,原因很简单,高性能往往需要充足的资源,高扩展性又需要尽可能地节约资源。所以我们才说,软件设计与架构是一项“权衡”的艺术,我们的目的不是将各个方面都达到最优,因为这是不可能实现的任务,我们须要做的只是找到一个平衡点使整体最优。关于高扩展性和性能之间的平衡关系,我们很难有一个适合所有场景的黄金法则,这需要对具体场景的具体分析。

较之会话模式,单调模式能够处理更多的并发客户端,提供更好的吞吐量(Throughput)。对于量化我们的服务到底能够处理多少客户端,Juval Lowy在其著作《Programming WCF》中提出了这样一项经验性总结:在一个典型的企业应用中,并发量大概是所有客户端数量的1%(高并发情况下能达到3%),也就是如果服务端能够同时维持100个服务实例,那么意味着能为10 000个客户端提供服务。

关于服务实例的创建过程,其中会使用到诸如反射这样的相对影响性能的操作,但是在WCF应用中,真正影响性能是操作时信道的创建和释放。服务实例的激活和它们比起来,可以说是微不足道。但是,如果在应用中出现对基于相同服务代理的频繁调用,比如服务调用放在一个For循环中调用上百次,服务实例的创建带来的性能损失就不能不考虑了。

作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载于:https://www.cnblogs.com/artech/archive/2009/11/09/1598695.html

WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[中篇]相关推荐

  1. WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[上篇](转)...

    http://www.cnblogs.com/artech/archive/2009/11/05/1596925.html 服务调用的目的体现在对某项服务功能的消费上,而功能的实现又定义在相应的服务类 ...

  2. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)...

    WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序) 原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模 ...

  3. WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

    原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码 ...

  4. WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]

    WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过Ser ...

  5. WCF技术剖析之二十一:WCF基本异常处理模式[中篇]

    通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的错误信息仅仅限于服务端可见,并不会被WC ...

  6. WCF技术剖析之二十五: 元数据(Metadata)架构体系全景展现[WS标准篇]

    元数据实际上是服务终结点的描述,终结点由地址(Address).绑定(Binding)和契约(Contract)经典的ABC三要素组成.认真阅读过<WCF技术剖析(卷1)>的读者相对会对这 ...

  7. WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇]

    元数据的导出就是实现从ServiceEndpoint对象向MetadataSet对象转换的过程,在WCF元数据框架体系中,元数据的导出工作由MetadataExporter实现.MetadataExp ...

  8. WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?

    任何一个程序都需要运行于一个确定的进程中,进程是一个容器,其中包含程序实例运行所需的资源.同理,一个WCF服务的监听与执行同样需要通过一个进程来承载.我们将为WCF服务创建或指定一个进程的方式称为服务 ...

  9. [原创]WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?

    任何一个程序都需要运行于一个确定的进程中,进程是一个容器,其中包含程序实例运行所需的资源.同理,一个WCF服务的监听与执行同样需要通过一个进程来承载.我们将为WCF服务创建或指定一个进程的方式称为服务 ...

最新文章

  1. flex 3.0序列号
  2. TensorFlow---基础---GFile
  3. hdu 4309(最大流+枚举状态)
  4. 平衡二叉树(AVL)--查找、删除、插入(Java实现)
  5. 「Python-StandardLib」第十六章:并发执行( Cocurrent Executing,线程、多线程队列、子进程)
  6. C++11新特性以及std::thread多线程编程
  7. 如何把一个字符串填充到一个无类型的指针 - 回复 豪杰的爸爸 的问题
  8. java 基础数据结构源码详解及数据结构算法
  9. 无锁并发框架Disruptor学习入门
  10. 03-【nginx】nginx源码分析--proxy模式下nginx的自动重定向auto_redirect
  11. 2021年塔式起重机司机考试题库及塔式起重机司机模拟考试
  12. android手表微信运动,华为手表(华为运动健康APP)终于能绑定微信运动
  13. el-descriptions
  14. 最大化印刷MES管理系统价值,提升印刷车间效率与质量
  15. 三阶魔方层先法自动复原_Python
  16. html5辨别音高,音理知识基础:音高和时值
  17. 11-聊天机器人项目准备
  18. 掌握电商后台设计,这一篇足矣 | 万字长文
  19. 医药健康API开放接口
  20. (1)基础学习——图解pin、pad、port、IO、net 的区别

热门文章

  1. Java_Web--JDBC 增加记录操作模板
  2. [图神经网络] 图神经网络GNN基础入门
  3. Opencv3编程入门学习笔记(三)之访问图像像素的三种方法
  4. 关于lwip中pbuf_alloc()内存申请函数
  5. Eigen入门之密集矩阵 4 - 块操作
  6. 【卷积核设计】Scaling Up Your Kernels to 31x31: Revisiting Large Kernel Design in CNNs
  7. 感知算法论文(二)Pelee: A Real-Time Object Detection System on Mobile Devices(2018)译文
  8. 营销大数据分析 关键技术_营销分析的3个最关键技能
  9. 在Ubuntu上创建并测试GRE tunnel
  10. 嘉年华回顾丨阿里云吕漫漪带你一览POLARDB整体架构设计...