WCF面向服务应用程序系列之十八:消息交换模式(MEP)-回调操作

      回调操作:是服务契约的一部分,取决于服务契约对回调契约的定义。一个服务契约最多只能包含一个回调契约,一旦定义了回调契约,就需要客户端支持回调,并在每次调用中提供指向服务的回调终结点,可以通过ServiceContract特性提供的Type类型的属性CallbackContract来定义回调契约。为了托管一个回调对象,客户端需要实例化回调对象,然后通过它创建一个上下文对象。

      在客户端与服务端通讯过程中,只要服务终结点的契约定义了一个回调契约,客户端都必须使用代理创建双向通讯,并将回调终结点的引用传递给服务。只要客户端正在等待回调,就不能关闭代理,如果关闭回调终结点,当服务试图将调用返回时,就会导致服务端产生错误。因此,可以由客户端自身实现回调契约,把代理定义为成员变量,当释放客户端时关闭代理。

      回调契约只能在支持双向通讯的端点上暴露,并非所有的绑定都支持回调操作,只有具有双向能力的绑定才能够用于回调:

      A:NetTcpBinding支持双向绑定(Duplex Binding);

      B:NamedPipeBinding支持双向绑定(Duplex Binding);

      C:WSDualHttpBinding提供了两个信道以支持双向通讯(它实际上设置了两个HTTP通道:1个是从客户端到服务的调用,另一个则用于服务到客户端的调用);

           1、混合双向通讯;

           2、两个OneWay WSHttpBinding信道;

      因HTTP本质上是与连接无关的,所以它不能用于回调。因此,我们不能基于BasicHttpBinding或WSHttpBinding绑定使用回调。

      在回调过程中,并发管理、同步访问与死锁是重点考虑的问题。在默认情况下,服务类被配置为单线程访问:服务实例与锁关联,在任何时间都只能有一个线程拥有锁,也只能有一个线程能够访问服务实例。在操作调用期间,向客户端发出的调用需要阻塞服务线程,并调用回调。然而一旦回调要返回它所需要的同一个锁的所有权,则处理从客户端返回的应答消息就会导致死锁。指当回调的应答消息也需要获得与服务实例关联的相同的锁时,就会导致死锁。因为此时服务线程已经被阻塞,服务操作正在等待回调操作执行完毕,而回调操作却又大等待服务释放锁,自然会产生锁的争用。这属于正在调用的会导致死锁的客户端的回调。

      如果单线程的服务实例试图将调用返回给它的客户端,为了避免免死锁,WCF会抛出一个InvalidOperationException异常。有三种可能解决方案:

      第一种方案:配置服务允许多线程访问,由于它与锁无关,因此允许回调。但是,也可能会增加服务开发者的负担,因为它需要为服务提供同步。

      第二种方案:将服务配置为重入(Reentrancy),指将服务的并发行为配置为重入([ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)])。一旦配置为重入,则服务实例仍然与锁关联,同时只允许单线程访问,然而,如果服务正在回调它的客户端,WCF就会首先释放锁。

      重入(Reentrancy):指对同步域拥有独占访问权的线程A调用了同步域之外对象的方法,此时另外的线程B若要访问该同步域,则线程A将释放对同步域的锁,允许线程B进入,直到线程B执行完毕并释放对同步域的锁后,线程A将重新进入该同步域。配置回调为重入时,因为服务对象是与线程关联的,属于同步域的对象,而回调对象则属性同步域之外的对象。由于服务被配置为重入,则服务调用回调引用时会释放锁,然后回调返回给客户端,控制权则返回给服务,服务会重入并重新获取锁。

      第三种方案:将回调契约操作配置为单向操作,这样服务就能够安全地将调用返回给客户端。因为没有任何应答消息会竞用锁,即使并发被设置为单线程,服务也能够支持回调。

      与ChannelFactory<T>类相似,WCF同样提供了DuplexChannelFactory<T>类,它被用于通过编程方式设置双向代理,与ChannelFactory<T>不同的是,它的构造函数既能接收回调实例,又能接收回调上下文。

      下面通过一个DEMO来介绍重入和单向操作的回调契约:

      主要代码如下:

      契约接口代码:

 
  
[ServiceContract(Namespace = " http://schemas.xinhaijulan.com/demos/Duplex " , CallbackContract = typeof (IMyContractCallback))]
public interface IMyContract
{
[OperationContract(IsOneWay
= true )]
// [OperationContract]
void HelloWCF( string msg);
}

public interface IMyContractCallback
{
[OperationContract(IsOneWay
= true )]
// [OperationContract]
void HelloWCFCallback( string msg);
}

      服务端契约实现类代码:

 
  
// [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class MyContract : IMyContract
{
public void HelloWCF( string msg)
{
Console.WriteLine(
" Message from client: " + msg);
IMyContractCallback callback
= OperationContext.Current.GetCallbackChannel < IMyContractCallback > ();
callback.HelloWCFCallback(
" This is the server message. " );
System.Threading.Thread.Sleep(
3000 );
}
}

      服务端代码:

 
  
static void Main( string [] args)
{
using (ServiceHost host = new ServiceHost( typeof (ServiceLibrary.MyContract)))
{
host.AddServiceEndpoint(
typeof (ServiceLibrary.IMyContract), new NetTcpBinding(), " net.tcp://localhost:9001/MyContract " );
host.Open();

Console.WriteLine(
" Please input exit to close host. " );
string key = Console.ReadLine();
while (key.ToLower() != " exit " )
{
Console.WriteLine(
" Please input exit to close host. " );
key
= Console.ReadLine();
}
}
}

      回调接口客户端实现类代码:

 
  
public class MyContractCallback : ServiceLibrary.IMyContractCallback
{
public void HelloWCFCallback( string msg)
{
Console.WriteLine(
" Message from me callback: " + msg);
System.Threading.Thread.Sleep(
3000 );
}
}

      客户端代码:

 
  
static void Main( string [] args)
{
ServiceLibrary.IMyContractCallback callback
= new MyContractCallback();
InstanceContext context
= new InstanceContext(callback);
DuplexChannelFactory
< ServiceLibrary.IMyContract > channelFactory = new DuplexChannelFactory < ServiceLibrary.IMyContract > (context, new NetTcpBinding());

ServiceLibrary.IMyContract proxy
= channelFactory.CreateChannel( new EndpointAddress( " net.tcp://localhost:9001/MyContract " ));

using (proxy as IDisposable)
{
Console.WriteLine(
" ------------Test Duplex Begin------------- " );
proxy.HelloWCF(
" This is the client message. " );
Console.WriteLine(
" ------------Test Duplex End--------------- " );

Console.ReadLine();
}
}

      测试OneWay下的回调契约,先运行Server,然后运行Client。

      服务端输出如下:

 
  
Please input exit to close host.
Message from client:This
is the client message.

      客户端输出如下:

 
  
------------Test Duplex Begin-------------
------------Test Duplex End---------------
Message from me callback:This is the server message.

      从以上输出可以看出,使用OneWay的回调契约测试成功,且在客户端调用服务端方法和服务端调用回调方法时没有阻塞。

      修改接口契约方法,不使用 OneWay,代码如下:

 
  
[ServiceContract(Namespace = " http://schemas.xinhaijulan.com/demos/Duplex " , CallbackContract = typeof (IMyContractCallback))]
public interface IMyContract
{
// [OperationContract(IsOneWay = true)]
[OperationContract]
void HelloWCF( string msg);
}

public interface IMyContractCallback
{
// [OperationContract(IsOneWay = true)]
[OperationContract]
void HelloWCFCallback( string msg);
}

      修改服务端契约实现类方法的并发类型为重入(Reentrant),代码如下:

 
  
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class MyContract : IMyContract
{
public void HelloWCF( string msg)
{
Console.WriteLine(
" Message from client: " + msg);
IMyContractCallback callback
= OperationContext.Current.GetCallbackChannel < IMyContractCallback > ();
callback.HelloWCFCallback(
" This is the server message. " );
System.Threading.Thread.Sleep(
3000 );
}
}

      重新编译服务端代码,和客户端代码,然后重新测试,先运行服务端,再运行客户端。

      服务端输出如下:

 
  
Please input exit to close host.
Message from client:This
is the client message.

      客户端输出如下:

 
  
------------ Test Duplex Begin -------------
Message from me callback:This
is the server message.
------------ Test Duplex End ---------------

      从以上输出可以看出,使用并发模式为重入(Reentrant)的回调契约测试成功,且在客户端调用服务端方法和服务端调用回调方法时发生了阻塞。

      至此,消息交换模式(MEP)-回调操作介绍完毕。

      点击下载DEMO

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

转载于:https://www.cnblogs.com/xinhaijulan/archive/2011/01/09/1931272.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值