我一直在努力使用 WCF 代理。处置 WCF 代理的正确方法是什么?答案并非微不足道。
System.ServiceModel.ClientBase 违反了微软自己的 Dispose 模式
System.ServiceModel.ClientBase<TChannel>
确实实现IDisposable
了,因此必须假定它应该在using
-block 中处理或使用。这些是任何一次性用品的最佳做法。但是,该实现是明确的,因此必须将ClientBase
实例显式转换为IDisposable
,从而使问题变得模糊。
然而,最大的混乱来源是调用出错Dispose()
的ClientBase
实例,甚至是因为它们从未打开过而出错的通道,都会导致抛出异常。这不可避免地意味着当堆栈展开时,解释错误的有意义的异常会立即丢失,using
范围结束并Dispose()
抛出一个无意义的异常,说明您无法处理有故障的通道。
上述行为是对dispose模式的诅咒,该模式指出对象必须能够容忍多次显式调用Dispose()
. (请参阅http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx,“ ...允许Dispose(bool)
多次调用该方法。该方法可能选择不执行任何操作在第一次通话之后。 ”)
随着控制反转的出现,这种糟糕的实现成为一个真正的问题。IOC 容器(特别是 Ninject)检测IDisposable
接口并Dispose()
在注入范围结束时显式调用激活的实例。
解决方案:代理 ClientBase 并拦截对 Dispose() 的调用
ClientBase
我的解决方案是通过子类化代理System.Runtime.Remoting.Proxies.RealProxy
并劫持或拦截对Dispose()
. 我的第一个替代品Dispose()
是这样的:
if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();
(请注意,这_client
是对代理目标的类型化引用。)
NetTcpBinding 的问题
起初我以为这已经解决了,但后来我发现了生产中的一个问题:在某些极其难以重现的场景下,我发现使用 a 的通道NetTcpBinding
在无故障的情况下没有正确关闭,即使Dispose
正在被调用_client
.
我有一个 ASP.NET MVC 应用程序,它使用我的代理实现连接到使用NetTcpBinding
本地网络上的 WCF 服务,托管在只有一个节点的服务集群上的 Windows NT 服务中。当我对 MVC 应用程序进行负载测试时,WCF 服务(使用端口共享)上的某些端点会在一段时间后停止响应。
我努力重现这一点:在两台开发人员机器之间跨 LAN 运行的相同组件运行良好;一个控制台应用程序锤击真正的 WCF 端点(在暂存服务集群上运行),每个工作中有许多进程和许多线程;在登台服务器上配置 MVC 应用程序以连接到负载下工作的开发人员机器上的端点;在开发人员的机器上运行 MVC 应用程序并连接到暂存 WCF 端点有效。然而,最后一个方案只适用于 IIS Express,这是一个突破。在开发人员机器上的全脂 IIS 下对 MVC 应用程序进行负载测试时,端点将被占用,连接到暂存服务集群。
解决方案:关闭通道
在未能理解问题并阅读了许多 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();
...并且该问题在所有测试设置中以及在暂存环境中的负载下都不再发生!
为什么?
谁能解释可能发生了什么以及为什么Channel
在调用之前明确关闭Dispose()
解决了它?据我所知,这不应该是必要的。
最后,我回到开头的问题:Dispose WCF Proxy 的正确方法是什么?我的替代品是否Dispose()
足够?