技术开发 频道

WCF操作、回调与重入


   注意,上述的代码中直接由客户端实现了回调契约,这是一种比较常见的实现方式。

   客户端通过回调传递给服务端的消息包含了回调契约终结点的引用。在服务端,可以通过OperationContext类的泛型方法GetCallbackChannel<T>()获得。如下所示:
ISomeCallbackContract callback = OperationContext.Current. GetCallbackChannel<ISomeCallbackContract>( );
   服务对回调的调用可能会产生死锁。例如,当客户端执行服务操作时,向客户端发出的调用会阻塞服务端进程,以等待服务操作执行完毕。而在该服务操作中,又获得了回调契约对象的引用(或者获得保存的回调契约副本),并执行回调操作。由于服务类被配置为单线程访问,则服务实例是与锁相关联的。如果回调对象也需要返回同一个锁的所有权,简单的说,就是指当回调的应答消息也需要获得与服务实例关联的相同的锁时,就会导致死锁。因为此时服务线程已经被阻塞,服务操作正在等待回调操作执行完毕,而回调操作却又在等待服务释放锁,自然会产生锁的争用。

   解决死锁的办法有三个,一个是将服务配置为允许多线程访问,但这会增加服务开发者管理多线程的负担。第二个方案是将回调设置为重入(Reentrancy),如下所示:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] 
class MyService : IMyContract
{
public void DoSomething( )
{
IMyContractCallback callback = OperationContext.Current.
GetCallbackChannel<IMyContractCallback>( );
callback.OnCallback( );
}
}
   所谓“重入”,是指对同步域拥有独占访问权的线程A调用了同步域之外对象的方法,此时,另外的线程B若要访问该同步域,则线程A将释放对同步域的锁,允许线程B进入。直到线程B执行完毕并释放对同步域的锁后,线程A将重新进入该同步域。配置回调为重入时,因为服务对象是与线程关联的,属于同步域的对象,而回调对象则属于同步域之外的对象。由于服务被配置为重入,则服务调用回调引用时会释放锁。然后将回调返回给客户端,控制权则返回给服务,服务会重入并重新获取锁。这样就解决了死锁的问题。

   第三种方案则是将回调操作设置为单向操作。此时,回调调用不会产生应答消息,服务操作一旦执行了回调操作,就会继续执行,回调对象不会争用与服务实例关联的锁,从而解决了死锁问题。

interface IMyContractCallback 
{
[OperationContract(IsOneWay = true)]
void OnCallback( );
}
   在使用回调对象时,需要考虑到客户端代理可能会被关闭,如果此时调用回调,就会引发一个ObjectDisposedException异常。“因此,对于客户端而言,当它不再需要接收回调或者客户端应用程序已经关闭时,最好能够通知服务。”本书给出了解决这一问题的方法,就是为服务契约增加两个操作Connect()与Disconnect()。其中,Disconnect()正是起到了通知服务的作用,它在客户端代理关闭的情况下,可以将当前的回调对象引用从列表中移除。至于Connect()方法则是出于对称的目的而引入,但引入它还有一个好处是,它可以使得客户端能够多次地连接或断开。实现Connect()与Disconnect()方法的代码如下:
static List<IMyContractCallback> m_Callbacks = new List<IMyContractCallback>( ); 
public void Connect( )
{
IMyContractCallback callback = OperationContext.Current.
GetCallbackChannel<IMyContractCallback>( );
if(m_Callbacks.Contains(callback) == false)
{
m_Callbacks.Add(callback);
}
}
public void Disconnect( )
{
IMyContractCallback callback = OperationContext.Current.
GetCallbackChannel<IMyContractCallback>( );
if(m_Callbacks.Contains(callback) == true)
{
m_Callbacks.Remove(callback);
}
else
{
throw new InvalidOperationException("Cannot find callback");
}
}
0
相关文章