[WCF]Instance Management
[有兴趣阅读本文的请从头至尾阅读,有兴趣帮助我解答疑问的请从尾至头读(红色部分),万分感谢!]
我们很容易理解在旧有编程模型中关于类实例的内容。设计模式中Singleton也就是在描述着档子事。但基于WCF并非适合于以上场景,Service与Client之间要保持良好的Instance模型则需要依靠很多其他机制。
Programming WCF Service Chapter4 对此进行了细致的描述。(更多细节请自行阅读~)
WCF支持三种类型的Instance管理:
1、pre-call services:每个客户端请求对应一个instance
2、Sessionful services:每个客户端连接对应一个instance
3、Singleton services:所有客户端共享一个instance
利用Behaviors可以解决这方面的问题(还有一些其他基于“服务端”的其他方面的问题可以通过使用behaviors来解决)。
注:客户端是不知道服务端设置了什么样的behaviors的。
Apply the ServiceBehaviorAttribute attribute to a service implementation to specify service-wide execution behavior. (To specify execution behavior at the method level, use the OperationBehaviorAttribute attribute.) This attribute can be applied only to service implementations.
ServiceBehaviorAttribute:仅应用于服务实现。
OperationBehaviorAttribute:用于方法级别。
public interface IMyContract
{}
//而是设置在了具体服务实现上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyContract : IMyContract,IDisposable
{}
设置instance模式类型由ServiceBehaviorAttribute的属性InstanceContextMode进行设置,默认值为PerSession.
Per-Call Services
只有当客户端调用的时候才有instance。
为了说明问题,书中用了很形象的例子。
Code:
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
[OperationContract]
void Count();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract,IDisposable
{
//Other Members
public MyContract()
{
Trace.WriteLine("WcfServiceLibrary1.MyContract()");
}
#region IMyContract Members
int count = 0;
public void Count()
{
count++;
Trace.WriteLine("Counter = " + count);
}
#endregion
#region IDisposable Members
public void Dispose()
{
Trace.WriteLine("WcfServiceLibrary1.Dispose()");
}
#endregion
}
//Tester
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient();
proxy.Count();
proxy.Count();
proxy.Count();
proxy.Close();
Console.ReadKey();
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
很明显,每次的值都是0+1的结果,这正说明了percall的方式是每个请求一个Instance的。
Per-Session Services
修改上面的例子:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
Counter = 2
Counter = 3
WcfServiceLibrary1.Dispose()
很明显Instance只有一个了。
我们WcfServiceLibrary默认的Bind是wsHttpBinding,但若是basicHttpBinding,由于每个http到达服务端都是一个新的连接,因此服务端无法判断是哪个连接。
增加服务端app.config中Endpoint。
<endpoint address="basic" binding="basicHttpBinding" name="basic" contract="WcfServiceLibrary1.IMyContract" />
重新导入后修改Program里的程序:
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
或
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
其中basic和WSHttpBinding_IMyContract为两种不同形式的服务在客户端的Endpoint.Name。
之前默认WSHttpBinding_IMyContract,现在由于存在多个Endpoint,则需要显示指定。
现指定为basic。再次运行,结果:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
其结果与PerCall是相同的。
通过SessionId可以获得Instance的SessionId
使用Per-Session方式可以通过设置SessionMode属性(允许、必须、不允许三种枚举)。
SessionMode:Gets or sets a value that indicates whether a session is required by the contract.
SessionMode 枚举http://msdn2.microsoft.com/zh-cn/library/system.servicemodel.sessionmode.aspx
Allowed(允许) 如果绑定支持Session的话,则让其支持,否则按照可以支持的方式,比如PerCall的方式进行支持。 |
Required(必须) 指定契约必须使用Sessionful的方式。如果不支持,则抛出异常。 |
NotAllowed(不允许) 指定不能使用Sessionful的方式。作者推荐是用NotAllowed的时候仅用PerCall方式。 |
刚才由于我添加了basic的方式,因为默认选中Allowed,因此刚才的之所以结果与PerCall相同,是因为它,下面我将其修改为Required。
将
[ServiceContract]
修改为
[ServiceContract(SessionMode=SessionMode.Required)]
结果为一个运行时错误:
System.InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it. |
但是若使用wsHttpBinding,但却without security and without reliable messaging也将无法维持transport-level session。
添加如下代码到app.config(Service)(指定其为无安全并且不可靠消息) <system.serviceModel><!-- Other service Model -->
<bindings> <wsHttpBinding> <binding name="wsBinding"> <reliableSession enabled="false" /> <security mode="None" /> </binding> </wsHttpBinding> </bindings> </system.serviceModel>
|
<endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary1.IMyContract" bindingConfiguration="wsBinding" >
|
确保服务端程序为: [ServiceContract(SessionMode = SessionMode.Allowed)] (或者没设) [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] |
运行结果与PerCall的结果相同。其原因也就是因为wsHttpBinding未设置安全可靠的Session。
超时
inactivityTimeout:超时时间
在连接空闲的情况下,以客户端和服务端的超时时间中最短的那个来决定是否移除Instance,若之后再调用则会抛出异常。
作者额外注明可以采用:AutomaticSessionShutdown属性。其设置为true则当proxy.Close()的时候自动关闭Session,设为false的时候则只有在服务端将服务关闭才会关闭Session。
但是,若将其修改为NotAllowed
则结果与PerCall相同(手动写为PerSession)。(不管服务配置如何,它总会是PerCall。因为TCP和IPC协议总是维持transport level,你不能将它们配置SessionMode.NotAllowed,它们会在服务载入时进行验证。作者建议是“在选择使用SessionMode.NotAllowed的同时,将服务配置为PerCall”。)
Singleton Service
Singleton,顾名思义就是仅有一个Instance,供所有客户端调用。
在说明问题之前先修改上面的例子:
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]为:[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] |
Tester中:
{
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy.Count();
proxy.Count();
proxy.Count();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
proxy.Close();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy1 = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy1.Count();
proxy1.Count();
proxy1.Count();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
proxy1.Close();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy2 = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
proxy2.Count();
proxy2.Count();
proxy2.Count();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
proxy2.Close();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
}
运行的结果:(Output<Debug>)
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9
//...
//其他代码
//...
从Counter的值看来,多个proxy调用的是同一个Instance。
值得一提的是WcfServiceLibrary1.MyContract(),也就是构造函数的调用时间是在Service启动的时候,而PerCall与Sessionful构造函数调用时间都是在proxy调用之时。而且只有当Host关闭的时候才会Dispose()。
MSDN:
If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to Multiple.
也就是说除非将服务设置成多线程的,否则在一个时间只能处理一个消息请求。
从HOST端控制SingletonInstance
方式一:
修改代码:
static void Main(string[] args)
{ Uri baseAddress = new Uri(http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary1/MyContract/); WcfServiceLibrary1.MyContract singleInstance = new WcfServiceLibrary1.MyContract(); singleInstance.CountProperty = 200;//设置初始值200 System.ServiceModel.ServiceHost host = new System.ServiceModel.ServiceHost(singleInstance); host.AddServiceEndpoint(typeof(WcfServiceLibrary1.IMyContract), new WSHttpBinding(), baseAddress); host.Open(); Console.WriteLine("host state = {0}", host.State.ToString()); ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract"); proxy.Count(); proxy.Count(); singleInstance.CountProperty = 100;//修改singletonInstance.CountProperty为100 proxy.Count(); Console.WriteLine(proxy.Endpoint.Name); Console.WriteLine(proxy.InnerChannel.SessionId); proxy.Close(); Console.WriteLine(proxy.Endpoint.Name); Console.WriteLine(proxy.InnerChannel.SessionId); Console.WriteLine("_________________________________________________________"); Console.ReadKey(); host.Close(); } |
向 public class MyContract 添加属性: public int CountProperty
{ set { count = value; } } |
无须启动WcfServiceLibrary1(将用外部Host进行启动)直接运行客户端程序:
结果:
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 201
Counter = 202
Counter = 101
//...
//其他代码
//...
方式二:
通过OperationContext.Current.Host 来获取当前进程的host
此方法我暂时未调出来,大家有想到或做到的麻烦告诉我!
MyContract singletonInstance = host.SingletonInstance as MyContract;
if (singletonInstance != null)
singletonInstance.CountProperty = DateTime.Now.Second;
先假设以上方法可行吧。
现在我遇到的问题:
1、OperationContext.Current这里的(MSDN:Gets or sets the execution context for the current thread.
)current thread是指我ConsoleApplication也就是Client的线程呢,还是指Service端的线程?(据我常理分析应该是服务端的线程)。理论上我应该在Count()方法内写这段代码,但是问题又涉及到通过Client端进行调用时是使用proxy,这样真正的情况会是怎样呢?
2、因此我又写了一个在一个ConsoleApplication里完成服务的代码,此时在host.SingletonInstance的确是被赋值了(不再是null了),但是紧接着我调用OperationContext.Current却发现其为null,也就是说这里的OperationContext并没有被赋值。继而将其转移到同在一个程序内的MyContract.Count()方法中,但是其值仍然为空,因此此法再度失效。
希望作为高手的您能够提供一个使用OperationContext.Current的场景。什么样算是OperationContext的当前线程?
推荐阅读:
http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WCFEssentials.mspx?mfr=true
posted on 2008-07-18 20:53 volnet(可以叫我大V) 阅读(1811) 评论(1) 编辑 收藏 举报