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

在[第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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262294.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VIM笔记

-->关于打开、保存和退出 :wq! 强制保存退出:wq 保存退出:w 保存文件:w! 强制保存文件:x ----- 作用和:wq 一样ZZ ---- 作用和:wq一样&#xff0c;(注意Z是大写的&#xff0c;并且不是在命令模式):q ---- 退出:q! --- 强制退出:e 打开文件:e! 强制打…

一份完整的问卷模板_一份完整市场推广策划方案模板

完整的市场方案主要分为以下几大板块&#xff0c;在策略撰写的过程中&#xff0c;依据策略优先级按照以下三个步骤划分&#xff0c;前后承接。为避免无用功&#xff0c;尽量确定前一部分后&#xff0c;再开始后一部分&#xff01;方案目录&#xff0c;记住这个&#xff01;整个…

Application是什么

2019独角兽企业重金招聘Python工程师标准>>> 1:Application是什么&#xff1f; Application和Activity,Service一样,是android框架的一个系统组件&#xff0c;当android程序启动时系统会创建一个 application对象&#xff0c;用来存储系统的一些信息。通常我们是不需…

linux mysql udf打包_Linux下MySQL 5.1编写UDF 并运行(Install)获取当前时间的毫秒数...

Mysql 无法获取当前时间的毫秒数自行定制UDF&#xff0c;以提供current_ms方法1. 编写 C 文件#ifdef STANDARD#include lt;stdio.hgt;#incMysql 无法获取当前时间的毫秒数自行定制UDF&#xff0c;以提供current_ms方法1. 编写 C 文件#ifdef STANDARD#include #include #ifdef _…

Daily Scrum 2012/11/08

TeamSH-IT 今天经完成了之前在数据定义存在一些问题。sui老师经过小组之间的交流&#xff0c;完成了对数据定义的最终版。周末将进行初步的整合和测试。 详细的完成情况&#xff1a; 组员今天任务明天任务Hu Renjun 任务213 整合各个模块功能 完成完整的数据流过程 任务213 …

部分不能激活Win 7 的问题分析和解决方案

前言 总所周知&#xff0c;目前Win7的激活程序多如牛毛&#xff0c;但总有些朋友的机器就是激活不了&#xff0c;以至于要刷BIOS等高危操作。 本文通过对Win7激活程序的原理分析&#xff0c;介绍一种能够解决大部分OEM版系统激活Win7的方法&#xff0c;希望能够帮助需要的朋友…

回调 that.setdata 数据不更新_重大利空落地,或损上亿利润,乐普医疗回调近四成...

摘要&#xff1a;心脏支架集采后&#xff0c;价格不足700元&#xff0c;预计其利润受损超亿元。11月5日&#xff0c;全国冠状支架招标结果公布后&#xff0c;医药行业遭受重创。11月6日收盘&#xff0c;医疗行业指数基金——医疗ETF()重挫&#xff0c;较早从事心血管介入医疗器…

android下创建文件夹和修改其权限的方法

原文&#xff1a;http://www.cnblogs.com/wanqieddy/archive/2011/12/28/2304906.html 由于工作的需要&#xff0c;今天研究了在android下创建文件夹和修改其权限的方法&#xff0c;需要了解的是每个应用程序包都会有一个私有的存储数据的目录&#xff08;类似文件夹&#xff0…

python中直方图-Numpy,Python中的“拉伸”直方图(级别)

这是一种方法- def stretch(a,lower_thresh,upper_thresh): r 255.0/(upper_thresh-lower_thresh2) # unit of stretching out np.round(r*(a-lower_thresh1)).astype(a.dtype) # stretched values out[a out[a>upper_thresh] 255 return out 根据OP,设置的标准是&#x…

Windows 2008 R2服务管理器刷新失败

在更新补丁的过程中&#xff0c;服务器强制重启后&#xff0c;在正常开机登录到系统后 打开服务器管理&#xff0c;角色及功能错误无法打开&#xff0c;参考以下链接解决。 参考 http://yewind.blog.51cto.com/33144/379097 http://social.technet.microsoft.com/Forums/en-US…

预处理指令

由ANSI的标准规定, 预处理指令主要包括: #define #error #if #else #elif #endif #ifdef #ifndef #undef #line #pragma 由上述指令可以看出, 每个预处理指令均带有符号"#"。下面只介绍一些常 用指令。 …

MFC字体

GDI字体分3类&#xff1a;点阵字体&#xff08;raster font&#xff09;、笔画字体&#xff08;stroke font&#xff09;和Truetype字体。 默认点阵字体有7种&#xff1a; System &#xff08;用于SYSTEM_FONT&#xff09; 无衬线 西方 有衬线 gb2312…

decimal double java_Java BigDecimal和double BigDecimal类

BigDecimal类对于不需要任何准确计算精度的数字可以直接使用float或double&#xff0c;但是如果需要精确计算的结果&#xff0c;则必须使用BigDecimal类&#xff0c;而且使用BigDecimal类也可以进行大数的操作。BigDecimal类的常用方法如表11-15所示。表11-15 BigDecimal类的常…

python自然语言处理案例-Python自然语言处理 NLTK 库用法入门教程【经典】

本文实例讲述了Python自然语言处理 NLTK 库用法。分享给大家供大家参考&#xff0c;具体如下&#xff1a; 在这篇文章中&#xff0c;我们将基于 Python 讨论自然语言处理&#xff08;NLP&#xff09;。本教程将会使用 Python NLTK 库。NLTK 是一个当下流行的&#xff0c;用于自…

分享45套2011年和2012年的高质量免费网站模板

日期&#xff1a;2012-11-11 来源&#xff1a;GBin1.com 前端时间我们分享了12套超酷的后台管理员界面网站模板&#xff0c;相信看过的朋友肯定已收入囊中了&#xff0c;今天呢&#xff0c;我们继续推荐45套高质量的免费网站模版&#xff0c;相信如果需要自己搭建网站的朋友肯…

基于PageRank的作弊检测算法

Spam Rank TrustRank Topical TrustRank Anti-Trust Rank HostRank BadRank 转载于:https://www.cnblogs.com/youwang/archive/2009/11/12/2310659.html

使用log4j日志-配置载入问题

1.在eclipse中&#xff0c;把log4j.properties放在类路径下&#xff0c;在项目启动时就会自己主动载入。2.在idea中。把log4j.properties放在类路径下&#xff0c;可是项目启动时不能直接载入&#xff08;原因不明白&#xff0c;哎&#xff0c;这个让我纠结了一下午&#xff01…

水晶报表中对某一栏位值进行处理_【节能学院】能耗管理系统在某超市嘉兴店二期工程的设计与应用...

摘要&#xff1a;随着社会生活水平的提高&#xff0c;经济的繁荣发展&#xff0c;人们对能源的需求逐渐增长&#xff0c;由此带来的能源危机日益严重。学校建筑如何实时的了解、分析和控制学校的能源消耗已成为需要解决的迫在眉睫的难题。传统的能源消耗智能以月/季度/年为周期…

python数据分析天气预报论文_用python+sklearn(机器学习)实现天气预报数据 模型和使用...

项目地址系列教程0.前言在上一篇教程里我们已经获取了所需要的全部数据&#xff0c;包括训练数据集和测试数据集&#xff0c;使用ProcessData()调用&#xff0c;所以接下来写模型的建立和预测1.建立模型没段代码在文章后面都会整合成一段&#xff0c;分段展示只是便于阅读a.准备…

偶然在网上看到的题目,jQuery功底如何一测便知晓!!!!!!

笔者最终实现的效果如下&#xff1a;    参考答案下载地址 通过手写html代码实现如图效果&#xff1a;以下题目全部使用jQuery来做 当点击“加载数据”按钮时根据提供的数据通过jQuery动态创建表格数据填充该表格。数据见data.txt文件。效果如图&#xff1a; 当点击“设置样…