处置 WCF 代理的正确方法是啥?
Posted
技术标签:
【中文标题】处置 WCF 代理的正确方法是啥?【英文标题】:What is the Correct Way to Dispose a WCF Proxy?处置 WCF 代理的正确方法是什么? 【发布时间】: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()
是否足够?
【问题讨论】:
【参考方案1】:据我所知,问题是调用Dispose
会释放句柄,但实际上并没有关闭通道,然后通道会保留资源,然后最终超时。
这就是为什么在负载测试期间您的服务在一段时间后停止响应的原因:因为初始调用保留资源的时间比您想象的要长,而后来的调用则无法利用这些资源。
我想出了以下解决方案。解决方案的前提是调用Dispose
应该足以处理掉句柄以及关闭通道。额外的好处是,如果客户端最终处于故障状态,则会重新创建它,以便后续调用成功。
如果ServiceClient<TService>
通过像Ninject
这样的依赖注入框架注入到另一个类中,那么所有资源都会被正确释放。
注意:请注意,在Ninject
的情况下,绑定必须定义一个范围,即它不能缺少InXyzScope
或使用InTransientScope
定义。如果范围没有意义,则使用InCallScope
。
这是我想出的:
public class ServiceClient<TService> : IDisposable
private readonly ChannelFactory<TService> channelFactory;
private readonly Func<TService> createChannel;
private Lazy<TService> service;
public ServiceClient(ChannelFactory<TService> channelFactory)
: base()
this.channelFactory = channelFactory;
this.createChannel = () =>
var channel = ChannelFactory.CreateChannel();
return channel;
;
this.service = new Lazy<TService>(() => CreateChannel());
protected ChannelFactory<TService> ChannelFactory
get
return this.channelFactory;
protected Func<TService, bool> IsChannelFaulted
get
return (service) =>
var channel = service as ICommunicationObject;
if (channel == null)
return false;
return channel.State == CommunicationState.Faulted;
;
protected Func<TService> CreateChannel
get
return this.createChannel;
protected Action<TService> DisposeChannel
get
return (service) =>
var channel = service as ICommunicationObject;
if (channel != null)
switch (channel.State)
case CommunicationState.Faulted:
channel.Abort();
break;
case CommunicationState.Closed:
break;
default:
try
channel.Close();
catch (CommunicationException)
catch (TimeoutException)
finally
if (channel.State != CommunicationState.Closed)
channel.Abort();
break;
;
protected Action<ChannelFactory<TService>> DisposeChannelFactory
get
return (channelFactory) =>
var disposable = channelFactory as IDisposable;
if (disposable != null)
disposable.Dispose();
;
public void Dispose()
DisposeChannel(this.service.Value);
DisposeChannelFactory(this.channelFactory);
public TService Service
get
if (this.service.IsValueCreated && IsChannelFaulted(this.service.Value))
DisposeChannel(this.service.Value);
this.service = new Lazy<TService>(() => CreateChannel());
return this.service.Value;
【讨论】:
以上是关于处置 WCF 代理的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 Anylogic 中为代理参数分配不同值的正确方法是啥?
在 WCF REST 服务中通过 POST 发送参数的正确 URI 是啥?