WCF从理论到实践(9):实例模式和对象生命周期

在上文WCF从理论到实践:事件广播 中,已经实现了完整的WCF服务端和客户端示例,其中也涉及到了远程对象实例创建的问题。本文就进一步的探索WCF中远程对象的创建模式和其生命周期

本文出发点:

通过阅读本文,您可以了解以下知识:

  1. WCF中有哪几种对象实例模式?
  2. 几种实例模式下对象的生命周期?
  3. 各种实例模式的应用场合?
  4. 使用不同的实例模式,需要注意的有哪些?
  5. 代码不骗人,用一个小范例来看看不同实例模式的区别?

本文适合的读者

本文适合有一定WCF基础知识的初学者

WCF中有哪几种对象实例创建模式?

WCF中有三种实例模式,这在.Net Framework中已经用InstanceContextMode枚举具体的列了出来,它们分别为:PerSession,PerCall,Single

几种实例模式下对象的生命周期?

PerCall

PerCall模式工作流程如下

  1. 客户端创建代理对象(Proxy)
  2. 客户端调用代理对象的一个契约操作,代理对象将其传递给服务宿主程序。
  3. 宿主应用程序创建一新的服务契约对象,并且执行请求操作
  4. 在执行完请求操作后,如果要求有应答,那么服务契约会给代理对象一个应答,然后销毁自己(如果实现了IDisposable,则调用Dispose())。

PerSession

PerSession模式工作的流程如下:

  1. 客户端创建代理对象(Proxy)
  2. 客户端第一次调用代理对象的一个契约操作,代理对象将其调用请求传递给服务宿主
  3. 宿主程序创建新的服务对象,并执行请求操作,如果有必要,返回客户端应答
  4. 客户端再次发出调用操作的请求,宿主会先判断是否已有建立好的会话,如果存在,则不需要再创建新的服务对象,直接使用老对象即可。
  5. 在时间达到指定要求或者因一些特殊原因,会话会过期,此时服务对象销毁。

Single

Single模式工作流程如下:

  1. 服务端启动,同时创建服务对象
  2. 客户端通过代理调用契约操作
  3. 第一步中创建的服务对象接受请求 ,并执行操作,进行必要的应答
  4. 第一步创建的服务对象将一直保留
  5. 服务关闭,第一步创建的对象销毁

各种实例模式的应用场合?

PerCall

在传统模式C/S模式的应用程序中,通常情况下存在这样的问题:客户端请求服务端之后,服务端并不是马上对处理客户端请求时需要的资源进行释放,服务端往往自作多情的认为客户端一定是个老主顾,会时不时来消费一番。可他却没想到,有的时候,客户端是个昧良心的家伙,就算服务端再怎么献殷勤,客户端也不买账。而对于一些非常珍贵的资源,比如数据库连接,文件,图像,通讯端口等。服务这种做法往往会使这些资源长期被不来消费的客户端空闲占用,当有新的请求真正要用使用他们的时候,却因为资源耗尽而无法处理。这样对服务端就得不偿失了,而PerCall就是对上面提到问题的一种解决方案。它采用类似快餐式的经营方式,当一个请求操作来到的时候,再创建服务对象,申请必要资源,而当操作完毕之后,立即销毁对象并释放资源,留给下一个请求。这就可能大大提高服务端的吞吐能力。而且WCF中默认的实例创建模式就是这种。

PerSession

正如上面对PerCall的描述所说,PerSession与传统的C/S模式应用程序非常相似,它能在服务端和客户端维护状态,当一个服务对象创建之后不会马上销毁,而是等待客户端再次来消费它,那这种的坏处也说过了,可能会浪费宝贵的服务资源,可它也是有好处的。比如它能够保持连接和维护状态,这在要求有回调的情况下特别重要,因为如果服务端连哪个家伙点的菜都忘记了怎么为客人上菜呢?还有一种情况,服务端操作不需要比较多的资源或者占用的资源也不宝贵的情况下,而却与客户端在不同的网络中,它们之间进行一次连接可费了老劲,这时也适用于此种实例模式。

Single

大家经常去理发吧?去那大的理发店,里面的理发师这家伙这个多,你随便找个就能帮你料理了,可兄弟我比较穷酸,每次都去小区理发店,里面连洗头,在理发就1位师傅,小区人可不少,僧多饭少,你说咱进去得排队吧。人家理发的时候,咱就得边上看着,得人家都整完了,嘿,咱就洗头,理发,吹风来个一条龙。Single模式就像上面提到的小区理发店,人家从早晨一开业,理发师就给你准备好了,您也甭挑蹦捡。伺候完你,他再伺候别人,为何要这么做?其实道理也很简单,如果理发师不喝水,不吃饭,不用管食宿,不用工钱,不会唧唧歪歪,那理发店老板娘肯定请1万个过来。可现实不是那样子的,是这些理发师都要吃喝拉撒睡,还要拿俸禄,就一个几十平米的小理发店,估计给老板娘卖了也不够他们的呢。

使用不同的实例模式,需要注意的有哪些?

  1. 对于PerCall模式,一定要记住,如果服务对象中的数据没有固化,并且不是静态变量,那它每次操作都会被重新初始化。
  2. 对于PerSession模式,第一要清楚有些Binding是不能用于此种模式的,具体什么可用,什么不能用,可以查阅http://www.cnblogs.com/jillzhang/archive/2008/02/03/1063406.html 。另外,PerSession模式并不是代表状态会自动维护,那些被设置了IsTerminating=True的操作完成的时候,也会释放资源和销毁对象。即使不是Ture,那如果客户端长时间不与服务端联系,达到服务端最大忍耐限度,服务端也会变心。
  3. 对于Single模式,既然例外就它老哥一个,就简单得多了,它能保持服务对象中的非静态全局变量。但是特别要注意的是,如果在这种模式下的话,要特别注意线程安全的问题,让10个人同时让一个理发师傅来服务。

代码不骗人,用一个小范例来看看不同实例模式的区别?

写了一个简单的范例,来验证和说明上述的观点,下面就先看一下最后的运行效果:

服务端运行效果解图:

从上面的解图可以看出,在服务启动的时候,也就是宿主ServiceHost.Open()的时候,便已经创建了Single模式的服务对象实例

客户端运行效果截图:

再回过头来看服务端效果:

本文参考资料

  1. http://msdn2.microsoft.com/zh-cn/magazine/cc163590(en-us).aspx

本文范例项目文件:

 /Files/jillzhang/Jillzhang.InstanceCtxMode.rar

作者:jillzhang
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2008-03-01 20:49 Robin Zhang 阅读(5225) 评论(40)  编辑 收藏 网摘 所属分类: WCF

  回复  引用  查看    
#1楼2008-03-02 11:36 | IamV      
继续支持!
  回复  引用    
#2楼2008-03-02 15:31 | 牛牛牛[未注册用户]
我每天都来看你
  回复  引用  查看    
#3楼2008-03-02 15:43 | fox23      
继续关注老张..
  回复  引用  查看    
#4楼[楼主]2008-03-02 16:22 | jillzhang      
@IamV
@牛牛牛
@fox23
多谢你们的支持,写的不好,还请多多包涵

  回复  引用    
#5楼2008-03-03 02:18 | 支持下[未注册用户]
不错
  回复  引用  查看    
#6楼2008-03-18 16:23 | tloner      
博主有没有考虑到另外一种情况:
1、创建一个实例消耗的时间和资源很大,等待一个单件来服务比较划算。
2、一个单件不能满足海量客户端并发。
这个时间就要创建一个实例池Instance pool,每次响应客户端请求时都要Pool里寻找空闲的Instance,如果没有空闲的,刚排队等待。适当改变Pool的大小能很好地解决海量并发访问。

上面的三种模式不能完美地解决这个问题。博主有什么解决思路吗?谢谢

  回复  引用  查看    
#7楼[楼主]2008-03-18 17:19 | jillzhang      
你说的情况好像很符合PerSession
------------------------------------------
一个用户一个,所以海量并发可以解决
同时解决了不必创建太多实例造成的性能损失

  回复  引用  查看    
#8楼2008-03-20 11:19 | tloner      
@楼主

在创建一个实例消耗的时间和资源很大的情况下,用PerSession和PerCall 都不太合适。
Instance Pool 才是理想方案,比如Com+的应用程序池

  回复  引用  查看    
#9楼2008-03-20 11:21 | tloner      
我想请教jillzhang关于WCF的Instance Pool的解决方案,谢谢-_-
  回复  引用  查看    
#10楼[楼主]2008-03-20 11:34 | jillzhang      
@tloner
我想你可能歪曲了概念,WCF中只存在这样三种实例模式,他们分别为PerCall,PerSession,Single,这个是无争得事实。任何人都无法改变它,首先你应该确认这一点。
那么现实情况真如你所说,在服务端某个操作可能浪费很大的资源,需要花费很长的时间,如果直接在服务契约对象中进行该操作,上述三种方法的确都各自有各自的弊端,但你可以跳出这个误区来,你的大操作完全可以在非服务契约中对象来进行,而你的服务契约只负责管理对象,给你这样一个情形,你可能更好理解,如果你有上万个客户端,要访问一个服务,而服务的操作是针对数据库的,那数据库连接就是你说的那种大操作对象。但你的服务对象并不是大操作,谁消耗资源,你就将谁放进池子,此时的设计不能好人坏人一起抓,对待服务对象,其实它可以只负知接受请求,和响应请求,但没必要非要让他自己再来处理请求吧?从这种角度看,WCF中的实例模型是正确的,只是你歪曲理解了概念,造成了设计上的困难。不知道我的说法你理解么?

  回复  引用  查看    
#11楼[楼主]2008-03-20 11:37 | jillzhang      
@tloner
我非常赞赏你希望通过池pool技术来增加系统的性能,但不能将一些原本和资源消耗无关的一部分对象也扔到池子里面,因为对待这部分来说,他们原本就不会游泳,扔进去,恐怕要淹死的。

  回复  引用  查看    
#12楼2008-03-20 15:16 | tloner      
@jillzhang
很感谢你的及时回复。
我也想过把大操作对象放到池子里,而服务契约对象只负责接受和响应请求。
正如你上面举的例子:把数据库连接放到池子里,每一次 Client Call 都要去池子里取一个空闲的数据库连接。
这个时候问题来了,如果实例模型是PerCall或PerSession,因为服务对象是由ClientCall触发创建的,如何让各个契约服务对象共用一个数据库连接池?
如果实例模型是Single,数据库连接池对它而言无意义了。



  回复  引用  查看    
#13楼2008-03-20 15:22 | tloner      
数据库连接池主要由操作系统或SQL控制,拿它来做例子不太适合,这里只是暂时引用罢了
  回复  引用  查看    
#14楼[楼主]2008-03-20 15:43 | jillzhang      
@tloner
PerCall下或者PerSession下也可以用静态的亚
static

  回复  引用  查看    
#15楼[楼主]2008-03-20 15:43 | jillzhang      
如果想让几个对象共用一个对象实例的话,静态不也是一种方法么?
  回复  引用  查看    
#16楼2008-03-20 16:13 | tloner      
Host服务的时候能够设置一些共享资源吗?比如我们上面说的池,以便于集中管理。我看过关于WCF的资源中,对Host的分析很少,主要集中的发布服务方面,除此之外它还有别的什么功能吗?

  回复  引用  查看    
#17楼[楼主]2008-03-20 16:21 | jillzhang      
从职责分明的角度来看,Host就是宿主,它不应该提供任何与业务相关的业务处理能力,它只不过是服务所生存的一个环境,你可以通过它指定服务的地址,服务使用的传输Bindding和服务的行为等,但这些都不是业务逻辑所关注的。如如果在Host的时候设置共享资源,那请问如果我想将原来在win service中寄存的服务现在改换为IIS中,那是不是还要对宿主程序进行二次开发?这是不现实的
  回复  引用  查看    
#18楼2008-03-20 16:35 | tloner      
多谢jillzhang的耐心指教!
  回复  引用  查看    
#19楼[楼主]2008-03-20 16:44 | jillzhang      
@tloner
共同交流

  回复  引用  查看    
#20楼2008-03-25 09:00 | tloner      
@jillzhang
关于同步模式,有点疑惑,如下例:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Single)]
class MyService : IMyContract
{...}

InstanceContextMode.PerCall表示每次请求都生成一个服务实例对象;
而ConcurrencyMode = ConcurrencyMode.Single用同步锁来每次只能有一个请求访问实例对象。
既然InstanceContextMode.PerCall,就不应该有同步访问同一个实例的情况了

  回复  引用  查看    
#21楼[楼主]2008-03-25 16:44 | jillzhang      
@tloner
首先InstanceContextMode和ConcurrencyMode二者控制的不是相同的问题
InstanceContextMode是用于控制对象实例模式的,它与对象的生命周期相关,而ConcurrencyMode控制的是服务的并发行为,诚如你所说,在PerCall的情况下,客户端每调用1次服务方法,服务端对象实例均有一次生命轮回,这是正确的,而ConcurrencyMode设置为Single则表示,在一个时间点,服务端只会处理一次请求,它并不是说一个服务实例只能接受一次请求,而是服务.此时如果InstanceContextMode被设置为PerCall,而ConcurrencyMode为Single,如果有一个请求正在访问服务的某个方法,另外一个请求来到的时候,它仍然会阻塞.使得另外一个请求不会得以处理,而是将这个请求进行排队处理.
ConcurrencyMode是针对ServerContract,而不是OperationContract的,明白了这个,也许就更能明白一些事情.如果还不能理解,我发给你一个范例,你运行下就能看出效果来

  回复  引用  查看    
#22楼2008-03-26 15:09 | tloner      
呵呵,茅塞顿开,不过我还是希望能收到范例,谢谢
  回复  引用  查看    
#23楼[楼主]2008-03-27 07:50 | jillzhang      
/Files/jillzhang/Jillzhang.Wcf.SingleAndPercall.rar
下载下去,将Service1中的Single换成Mutiple,看看效果的不同就更理解了

  回复  引用  查看    
#24楼2008-04-16 14:52 | tloner      
@jillzhang
你范例中当
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,ConcurrencyMode=ConcurrencyMode.Single)]
public class Service1 : IService1
{}请求并没有排队。


[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Single)]
public class Service1 : IService1
{}请求才会排队。

是否意味着InstanceContextMode=InstanceContextMode.PerCall的时候,ConcurrencyMode不起作用,但是我查了相关文档,没有这种说法。

  回复  引用  查看    
#25楼2008-04-18 11:09 | 81      
这个系统写的太好了。
对InstanceContextMode不太明白:
PerCall时不保存状态,每次产生新的,也就是说类的属性几乎是没有意义的,如果这样,我在ServiceContract中就只写Methor,就是一个纯函数类,然后把InstanceContextMode设置为Single, 因为纯函数,应该没有同步的问题。

我不明白的是在InstanceContextMode设置为Single的情况下,一个Instance应该可以同时为多个Client服务吧(当然是ConcurrencyMode.Multiple情况下),虽然是一个理发师,但他有无限多的手,可以同时为多个服务。如果这样,我们用Single代替PreCall不是更好吗?

  回复  引用  查看    
#26楼2008-04-18 11:11 | 81      
@tloner
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,ConcurrencyMode=ConcurrencyMode.Single)]
public class Service1 : IService1
{}请求并没有排队。
=============
这个对像是专门为你建立的,别人根本无法使用,当然不排队。

  回复  引用  查看    
#27楼[楼主]2008-04-18 11:36 | jillzhang      
PerCall时不保存状态,每次产生新的,也就是说类的属性几乎是没有意义的,如果这样,我在ServiceContract中就只写Method,就是一个纯函数类
---------------------------------------------------------------
这样说是对的
然后把InstanceContextMode设置为Single, 因为纯函数,应该没有同步的问题。
-----------------------------------------------------------
你好像打错了,应该是然后把ConcurrencyMode设置为Single吧?如果是的话,
如果
InstanceContextMode :Single
InstanceContextMode :Single
此时的服务操作相当于类型安全的。即使有静态变量在操作中,也不涉及到同步的问题
但如果是下面的情形
InstanceContextMode :Single
InstanceContextMode :Mutile
就不一样了,此时服务类型不是安全的,在操作中必须考虑同步问题
-----------------------------------------------------
我们用Single代替PreCall不是更好吗?
这样说就不对了,Single模式创建一个服务实例,且这个服务下的所有资源为客户端所共享。比如说这种情形,现在有10个宝石,分给不同的客户端,客户端每使用一个,宝石就会少一个。而Percall每次调用都会创建实例,而创建实例的时候就可能重新初始化资源。这就很好的解决了资源共享的问题。所以PerCall和Single还是有区别的,这种却别就像变量和静态变量的区别类似,PerSession是一个变态狂,明显不受框架开发人员的欢迎,因为它不太符合服务规范,但没有办法,用户需要。就像http中的cookie一样,本身http是没有状态的,cookie或者session是后来强奸人家才使其怀得孕,:)



  回复  引用  查看    
#28楼[楼主]2008-04-18 11:41 | jillzhang      
是否意味着InstanceContextMode=InstanceContextMode.PerCall的时候,ConcurrencyMode不起作用,但是我查了相关文档,没有这种说法
-----------------------------------------------------------------
其作用亚,如果此时ConcurrencyMode为Single,那服务的访问是顺序进行的。也就是A访问了服务的时候,此时B就不能访问,B不能访问当然也就不能创建实例了。

  回复  引用  查看    
#29楼2008-04-18 14:37 | 81      
InstanceContextMode :Single
InstanceContextMode :Mutile
就不一样了,此时服务类型不是安全的,在操作中必须考虑同步问题
======================================
一个类如果都是函数,当然不存在同步问题。我觉得。
我的理解是多个Client可以同时调用这个InstanceContextMode :Single的函数,你的解释是Client独占服务器的这个单实例。这个问题我有空做个实验看看。
谢谢。

  回复  引用  查看    
#30楼[楼主]2008-04-18 15:52 | jillzhang      
一个类如果都是函数,当然不存在同步问题。我觉得。
-------------------------------------------------
函数中总的有数据吧
代码+数据 = 程序,缺一不可



------------
InstanceContextMode :Single
InstanceContextMode :Mutile
这种情况下,多个客户是可以同时调用服务操作的,所以涉及到类型安全问题。

InstanceContextMode :Single
InstanceContextMode :Single
这种方法,因为只有一个实例,这个实例某一时刻只服务于一个用户,就解决了类型安全问题

你去银行取钱,如果银行只有一个柜台,大家都去这个柜台取
这就是InstanceContextMode :Single
如果必须按照序号,柜台只有完完全全服务完一个客户后才处理下一个,这就是

InstanceContextMode :Single,而如果在服务客户的过程中,因为某些原因,客户A的业务还没有办完,但需要等待一段时间,那柜台也可以先为下位服务,这就是
InstanceContextMode :Mutile

如果你一进银行,发现柜台就10来个,一个柜台就可以服务一个客户,这种就不是InstanceContextMode :Single了,但每个客户从柜台取得钱在银行是有限的,银行一共100W,10个柜台,每个取20W,就有人取不成了


  回复  引用    
#31楼2008-06-07 16:57 | ooxxxxxxxxx[未注册用户]
最后的例子图片中的percall,而在下载的例子代码中是调用了两次。为什么实例是4??
  回复  引用    
#32楼2008-06-07 17:03 | ooxxxxxxxxx[未注册用户]
我明白了,是每一个操作都会生成实例,实不仅仅是add操作。
  回复  引用    
#33楼2008-08-21 17:14 | 位绰[未注册用户]
走过路过不要错过
  回复  引用  查看    
#34楼[楼主]2008-08-22 09:14 | jillzhang      
@位绰
呵呵,多谢捧场

  回复  引用    
#35楼2008-11-21 10:07 | graystar[未注册用户]
如果使用persession模式的话,那这个session如果因为服务器的原因而停止的话,那客户端如何再能访问他呢.
  回复  引用  查看    
#36楼[楼主]2008-11-22 12:29 | jillzhang      
@graystar
和asp.net是一样的,属于负载均衡的问题。
if master server is bad,one slave should replace it!

  回复  引用  查看    
#37楼2008-12-06 16:55 | 蓉青姚      
LZ写得不错
以后多多关注你的文章

  回复  引用    
#38楼2009-01-16 22:58 | heart_string[未注册用户]
楼主强悍,有问题请教!

我的场景是这样的:客户端连接上服务端后,服务端保存客户端的回调接口,后续服务端根据业务场景来调用客户端的回调函数,即要保证从服务端到客户端的通信是有效的。
在persession模式下,经过一段时间后时从客户端到服务端的信道正常,能够正常调用服务契约,可从服务端到客户端的回调信道处于出错状态,无法调用客户端的方法,是不是因为从服务端到客户端的会话过期了?

我想了个办法,在服务端公布一个心跳的契约,客户端周期性的调用,然后服务端回调客户端的函数,这样来保持双方的会话的持久有效性

请问还有什么方法可以保证客户端、服务端的连接是永久的(网络正常的情况下)?

请赐教!




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1087467




历史上的今天:
2007-03-01 共享终结者-ShareKiller

相关文章:

相关链接: