我一直在苦恼于WCF代理的问题。正确的方式是什么来Dispose一个WCF代理呢?答案并不简单。
System.ServiceModel.ClientBase违反了微软自己的Dispose模式
System.ServiceModel.ClientBase<TChannel>
确实实现了IDisposable
,因此可以假设应该处理它或者在using
-块中使用。对于任何可处理事务,这都是最佳实践。但是,它的实现是显式的,因此必须显式地将ClientBase
实例转换为IDisposable
,从而使问题复杂化。
然而,最令人困惑的是,调用已故障的ClientBase
实例上的Dispose()
,甚至是从未打开过的通道,将导致抛出异常。这不可避免地意味着有意义的解释故障的异常会立即丢失,当堆栈展开时,using
范围结束,并且Dispose()
抛出一个无意义的异常,说您不能处置一个故障通道。
上述行为违反了处理模式,该模式指出对象必须容忍对Dispose()
进行多次显式调用。 (参见http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx,“...允许多次调用Dispose(bool)
方法。第一次调用后,该方法可能选择不做任何事情。”)
随着控制反转的出现,这种差劲的实现变成了一个真正的问题。I.O.C.容器(特别是Ninject)检测到IDisposable
接口,并在注入范围结束时显式地调用已激活实例上的Dispose()
。
解决方案:代理ClientBase并拦截对Dispose()的调用
我的解决方案是通过子类化System.Runtime.Remoting.Proxies.RealProxy
来代理ClientBase
,并获取或拦截对Dispose()
的调用。我的第一个替换Dispose()
如下:
if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();
请注意,_client
是代理目标的类型引用。
NetTcpBinding存在的问题
起初我以为这个方法解决了问题,但后来在生产环境中发现了一个问题:在某些极难复现的情况下,使用NetTcpBinding
的通道在未故障的情况下无法正确关闭,即使在_client
上调用了Dispose
方法。
我的ASP.NET MVC应用程序使用我的代理实现连接到本地网络上运行的使用NetTcpBinding
的WCF服务,该服务托管在一个只有一个节点的服务集群内的Windows NT服务中。当我对MVC应用程序进行负载测试时,WCF服务上的某些端点(使用端口共享)会在一段时间后停止响应。
我努力尝试重现这个问题:在两台开发人员机器之间运行的相同组件完美运行;使用许多进程和每个进程中的许多线程在真正的WCF端点(运行在暂存服务集群上)上测试控制台应用程序也能正常工作;将MVC应用程序配置在暂存服务器上以连接到开发人员机器上的端点,在负载下也能正常工作;在开发人员机器上运行MVC应用程序并连接到暂存WCF端点也能正常工作。然而,最后一种情况只在IIS Express下才有效,这是一个突破。当使用完整版的IIS在开发人员机器上连接到暂存服务集群时,端点会停止响应。
解决方案:关闭通道
在无法理解问题并阅读了许多MSDN和其他来源声称问题根本不存在的页面后,我尝试了一个可能成功的方法,将我的Dispose()
解决方法更改为...
if (_client.State == CommunicationState.Faulted) _client.Abort();
else if (_client.State == CommunicationState.Opened)
{
((IContextChannel)_client.Channel).Close();
((IDisposable)_client).Dispose();
}
else ((IDisposable)_client).Dispose();
……问题在所有测试环境和分阶段环境的负载下都停止出现了!
为什么?
请问有人能解释一下到底发生了什么,为什么在调用Dispose()
前明确关闭Channel
就可以解决这个问题吗?据我所知,这应该是不必要的。
最后,回到开头的问题:正确的WCF代理释放方式是什么?我的Dispose()
替代方案是否足够?