回调操作:是服务契约的一部分,取决于服务契约对回调契约的定义。一个服务契约最多只能包含一个回调契约,一旦定义了回调契约,就需要客户端支持回调,并在每次调用中提供指向服务的回调终结点,可以通过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)-回调操作介绍完毕。
作者:心海巨澜
出处:http://xinhaijulan.cnblogs.com
版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。