第十四讲 实例模式
Posted HulkXu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十四讲 实例模式相关的知识,希望对你有一定的参考价值。
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
不是特殊的Demo,我们不再贴实例Demo的图片了,直接去网盘找相应的项目看
实例上下文模式(IntanceContext Mode):
表示服务端的服务实例与客户端的服务代理的绑定方式。在WCF中有三种不同的实例上下文模式,单调(Per-Call)模式、会话(Per-Session)模式和单例(Single)模式。
1).单调模式:如果采用单调实例上下文模式,对于每一个服务调用,不论是来自相同的客户端(服务代理)还是不同的客户端,WCF总是创建一个全新的服务实例和实例上下文对象来对客户端的请求进行处理。在服务操作执行完毕,实例上下文对象和被封装的服务实例被回收掉.
2).会话模式:会话的目的在于保持来自相同客户端(即同一个服务代理)多次服务调用之间的状态。如果从消息交互的角度来讲,通过会话可以将来自相同客户端的多个消息关联在一起。在会话实例上下文模式下,WCF为每一个服务代理对象分配一个单独
的服务实例上下文对象,对于来自相同服务代理的所有服务调用请求,都将分发给相同的服务实例上下文处理。
3).单例模式:单例模式意味着WCF为每个服务维护一个并且仅维护一个服务实例上下文。不论请求来自相同的服务代理还是不同的服务代理,处理服务调用请求的都是同一个服务实例上下文对象。
实例服务行为:
在介绍服务寄宿的时候,我们谈到过WCF下”契约”和”行为”的区别:
契约是涉及双边的描述(契约是服务的提供者和服务消费者进行交互的手段),那么行为就是基于单边的描述。客户端行为体现的是WCF如何进行服务调用的方式,而服务端行为则体现了WCF的请求分发方式。所以服务契约会通过元数据对外发布,而服务行为对于客户端则是透明的。
对于客户端来讲,它所关心的是通过服务调用能否获得正确的结果,而不会关心服务端采用怎样的模式来激活服务实例。所以,WCF实例管理通过服务行为体现,不同的实例上下文模式通过ServiceBehaviorAttribute特性指定。
在ServiceBehaviorAttribute中,通过设置InstanceContextMode属性来指定不同的服务实例上下文模式.
1 public enum InstanceContextMode 2 { 3 PerCall, 4 PerSession, 5 Single 6 }
默认选项为PerSession(会话模式).
单调实例上下文模式(PerCall):
在单调实例上下文模式下,WCF总是创建一个新的服务实例上下文处理接收到的每一个服务调用请求,并在服务操作执行结束后,回收服务上下文和服务实例。换句话说,单调服务实例上下文模式使服务实例上下文的生命周期与服务调用本身绑定。也就是,对于单调模式,服务实例的生命周期大体上可以看成服务操作执行的生命周期。服务实例在服务操作执行前被创建,在操作完成之后被回收。
如果服务实例需要引用一些非托管资源,比如数据库连接,文件句柄等。需要及时将其释放。在这种情况下,我们可以通过实现IDisposable接口,在Dispose方法中进行相应的资源回收工作。
在单调实例上下文模式下,当服务操作执行时,Dispose方法会自动被执行,这一点通过下面的DEMO来证实。
我们看 我们本节课的 Demo ( 1.单调实例 ) 在云盘
看我们的 Demo 服务契约的实现:
1 //这里设置服务的行为 是 单调模式 2 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 3 public class CalculatorService : ICalculator, IDisposable 4 { 5 public CalculatorService() 6 { 7 Console.WriteLine("Service object is instantiated.服务被创建"); 8 } 9 public void Dispose() 10 { 11 Console.WriteLine("Service object is disposed.释放资源,在请求生命周期结束的时候释放资源"); 12 } 13 public double Add(double x, double y) 14 { 15 Console.WriteLine("Operation method is invoked.调用服务Add方法"); 16 return x + y; 17 } 18 ~CalculatorService() 19 { 20 Console.WriteLine("Service object is finalized.析构函数,执行顺序不确定,由垃圾回收制自动调用"); 21 } 22 }
客户端 我们写的是两次请求:
1 static void Main(string[] args) 2 { 3 using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice")) 4 { 5 ICalculator calculator = channelFactory.CreateChannel(); 6 Console.WriteLine("x+y={2} when x={0} and y={1}", 1, 2, calculator.Add(1, 2)); 7 Console.WriteLine("x+y={2} when x={0} and y={1}", 1, 2, calculator.Add(1, 2)); 8 Console.Read(); 9 } 10 }
看我们的测试结果是:
[ 14-01 ]
从运行后服务端的输出可以看出,对于两次服务调用请求,服务端先后创建了两个服务实例,在操作方法成功执行后,Dispose方法得以执行。而终止器(Finalizer)是被GC在后台执行的,所以执行的时机不能确定。
不过有一点可以从中得到证实:当服务操作执行时,服务实例变成了"垃圾"对象,并可以被GC回收以腾出占据的内存空间。
对于实现了IDisposable接口的Dispose方法,有一点值得注意的是:该方法是以与操作方法同步(几乎同步)的形式执行的。也就是说,服务操作和Dispose方法在相同的线程中执行。
认识这一点很重要,在支持会话的情况下如果服务请求来自于同一个服务代理,服务操作都会在下一个线程执行。
对于单调模式就会出现这样的问题:由于Dispose方法同步执行的特性,如果该方法是一个比较耗时的操作,那么来自于同一个服务代理的服务后续调用请求将不能得到及时执行。WCF只能在上一个服务实例被成功释放之后,才能处理来自相同服务代理的下一个服务调用请求.
在单调模式,如果不考虑GC对垃圾对象回收的滞后性,服务实例的数量可以看成是当前正在处理的服务调用请求的数量。相关的资源能够在服务操作执行完毕之后得到及时回收。所以,单调模式具有的优势是能够最大限度的发挥资源的利用率,避免了资源的
闲置和相互争用。这里的资源不仅仅包括服务实例本身占据的内存资源,也包括服务实例直接或间接引用的资源。由于单调模式采用基于服务调用的服务实例激活或资源的分配模式,所以服务实例或被分配的资源自始至终都处于“工作”状态,不会造成资源的闲置。服务实例在完成其使命之后,能够对资源进行及时的释放,被释放的资源可以及时用于对其他服务请求的处理。
我们将单调模式和后面要讲的会话模式作一个对比,后者采用基于服务代理的实例激活和生命周期管理。也就是说,在不考虑WCF闲置请求策略(当服务实例在超出某个时间段没有被使用的情况下,WCF将其清理)的情况下,服务实例的生命始于通过服务实例
进行第一次服务调用,或者调用OPEN方法开启服务代理之时,服务代理的关闭会通知WCF服务端框架将对应的服务实例进行释放。
举一个极端的会话模式的例子,服务实例在存续期间需要引用一个非托管资源,比如是数据库连接,假设允许的最大并发连接为100。现在,先后100个客户端(或者服务代理)进行服务调用请求,毫无疑问,100个服务实例会被创建并同时存在于服务端的内存之中,并且每一个服务实例引用一个开启状态的数据库连接,那么当来自第101个客户端服务调用请求抵达时,将得不到处理,除非在它的超时时限到达之前,有一个客户端自动将服务代理关闭。
但是,对于相同的场景,如果采用单调的模式,就能应付自如,因为每次服务调用之后,数据库的连接可以及时地得到关闭和释放.
所以上面 较之会话模式,单调模式能够处理更多的并发客户端。
关于服务实例的创建过程,其中会使用到诸如反射这样相对影响性能的操作,但是在WCF应用中,真正影响性能的是操作是信道的创建和释放。服务实例的激活和它们比起来,可以说微不足道。但是,如果在应用中出现对于相同服务代理的频繁调用,比如服务调用放在一个for循环中调用上百次,服务实例的创建带来的性能损失不能不考虑了。
所以单调模式的 最大有点就是 :最大限度的发挥资源的利用率,避免了资源的闲置和相互争用 ,还有就是 能够处理更多的并发客户端
单例(Single)实例上下文模式:
单例模式下,WCF通过创建一个唯一的服务实例来处理所有的客户端服务调用请求。这是一个极端的服务实例激活方式,由于服务实例的唯一性,所有客户端每次调用的状态能够被保存下来,但是当前的状态是所有客户端作用于服务实例的结果,它不能反映出具体某个客户端多次调用后的状态。WCF是一个典型的多线程的通信框架,对并发的服务调用请求是最基本的能力和要求,但是服务实例的单一性就意味着相同服务实例需要在多个线程下并发地调用。
我们看一个 Demo (本节课的 第二个 Demo 2.单例实例 )
首先看一下我们的服务契约实现:
1 //设置实例模式 为 单例 模式(Single) 2 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] 3 public class CalculatorService : ICalculator 4 { 5 private double _result; 6 public void Add(double x) 7 { 8 this._result += x; 9 } 10 public double GetResult() 11 { 12 return this._result; 13 } 14 }
然后 看 客户端:
1 static void Main(string[] args) 2 { 3 using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice")) 4 { 5 6 ICalculator calculator1 = channelFactory.CreateChannel(); 7 ICalculator calculator2 = channelFactory.CreateChannel(); 8 9 Console.WriteLine("1st service proxy(代理1):"); 10 Console.WriteLine(" 调用服务的方法 Add(3) ;"); 11 calculator1.Add(3); 12 Console.WriteLine("The result is (得到) {0}.\n", calculator1.GetResult()); 13 14 Console.WriteLine("2nd service proxy(代理2调用服务的Add方法)"); 15 Console.WriteLine(" 调用服务的方法 Add(3) ;"); 16 calculator2.Add(3); 17 Console.WriteLine("The result is (得到) {0}.", calculator2.GetResult()); 18 19 Console.Read(); 20 } 21 }
我们看测试结果:
[ 14-02 ]
结果等于6
在客户端,通过ChannelFactory<ICalculator>创建两个服务代理,模拟两个不同的客户端。从最终输出来看,得到的结果并不能反映出具体某个客户端正常的累加运算(对于通过calculator2模式的客户端,仅仅调用了一次Add(3),得到的结果却是6,这是所有客户端一起累加的结果,这就是服务实例的单一性造成的。)
与其它两种实例上下文模式(单调模式和会话模式)相比,单例模式具有不一样的服务实例创建方式。从服务实例创建的时机来看,单调服务实例创建于每一个服务调用,会话服务实例则创建于服务代理的显式开启(执行客户端的代理Open方法)或第一个服务调用,而单例服务实例则在服务寄宿之时。单例模式,既可以通过WCF提供的实例激活机制自动创建服务实例,也可以将创建好的对象作为服务实例,我们把这两种服务实例的提供方式分别称为隐式单例和已知单例。
我们将本节课的 第二个 Demo 修改下 寄宿的 代码 来 看 已知单例 和 隐式单例
已知单例:
1 static void Main(string[] args) 2 { 3 //这里定义服务契约的实现 4 CalculatorService calculatorService = new CalculatorService(); 5 //将服务契约的实现 的 实例 作为参数 6 using (ServiceHost serviceHost = new ServiceHost(calculatorService)) 7 { 8 serviceHost.Open(); 9 Console.Read(); 10 } 11 }
隐式单例:
1 static void Main(string[] args) 2 { 3 //将服务契约的实现对象 作为参数 4 using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService))) 5 { 6 serviceHost.Open(); 7 Console.Read(); 8 } 9 }
记住 只有在 单例模式(Single)下,才可以这么创建已知单例的这种写法。
一般地,在寄宿某个服务的时候,我们会指定服务的类型。WCF会根据服务类型,通过反射的机制,调用默认无参构造函数创建服务实例。但是,如果服务类型没有定义无参构造函数,或者我们需要手工对服务实例作一些初始化工作,WCF提供的实例激活机制
就不能为我们服务了。为了解决这种需求,需要自行创建服务实例,采用基于服务实例的寄宿方式来代替原来基于服务类型的寄宿方式。只有单例上下文模式才能采用这种寄宿方式,我们把这种基于现有服务对象的服务实例提供模式称为”已知单例模式”。
对于单例上下文模式,如果采用传统的基于服务类型的寄宿方式,即通过服务类型而非服务实例创建ServiceHost对象,服务实例是通过WCF内部的服务实例激活机制创建的。不同于其他两种实例上下文模式采用请求式实例激活方式(单调实例上下文在处理每次
调用请求时创建,而会话实例上下文模式则在接收到某个客户端的第一次调用请求时创建服务实例上下文),单例实例上下文模式在ServiceHost的初始化过程中被创建。我们把这种模式称为隐式单例模式.
以上是关于第十四讲 实例模式的主要内容,如果未能解决你的问题,请参考以下文章
《LeetCode零基础指南》(第十四讲) 力扣常见报错集锦