【发布时间】:2015-02-11 00:42:34
【问题描述】:
我一直在努力使用 WCF 代理。处置 WCF 代理的正确方法是什么?答案并不简单。
System.ServiceModel.ClientBase 违反了微软自己的 Dispose 模式
System.ServiceModel.ClientBase<TChannel> 确实 实现了IDisposable,因此必须假定它应该在using-block 中处理或使用。这些是任何一次性用品的最佳做法。然而,实现是显式的,因此必须将 ClientBase 实例显式转换为 IDisposable,从而使问题变得模糊。
然而,最大的混乱来源是,在出现故障的 ClientBase 实例上调用 Dispose(),甚至是因为一开始就没有打开而出现故障的通道,都会导致异常被抛出。这不可避免地意味着解释错误的有意义的异常在堆栈展开时立即丢失,using 范围结束并且Dispose() 抛出一个无意义的异常,说明您无法处理故障通道。
上述行为是对 dispose 模式的诅咒,该模式指出对象必须能够容忍对Dispose() 的多次显式调用。 (参见http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx,“...允许多次调用Dispose(bool) 方法。该方法可能选择在第一次调用后什么都不做。”)
随着控制反转的出现,这种糟糕的实现成为一个真正的问题。国际奥委会容器(特别是 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 的频道在正常情况下无法正常关闭,即使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();
...问题在所有测试设置中和在暂存环境中的负载下停止发生!
为什么?
谁能解释可能发生了什么以及为什么在调用Dispose() 之前明确关闭Channel 解决了它?据我所知,这应该没有必要。
最后,我回到开头的问题:Dispose WCF 代理的正确方法是什么?我替换Dispose() 是否足够?
【问题讨论】:
标签: asp.net-mvc wcf dispose nettcpbinding realproxy