我一直在努力使用WCF Proxies。处理WCF代理的正确方法是什么?答案并非无足轻重。
System.ServiceModel.ClientBase违反了微软自己的Dispose-pattern
System.ServiceModel.ClientBase<TChannel>
确实实施IDisposable
,因此必须假设它应该在using
- 块中处理或使用。这些是一次性用品的最佳实践。但是,实现是明确的,因此必须明确地将ClientBase
个实例强制转换为IDisposable
,这会使问题蒙上阴影。
然而,混淆的最大原因是在Dispose()
个实例上调用ClientBase
,甚至因为他们从未在第一时间打开而导致出错的渠道,导致异常被抛出。这不可避免地意味着当堆栈展开时,解释错误的有意义的异常立即丢失,using
范围结束,Dispose()
抛出无意义的异常,表示您无法处置故障通道
上述行为是 dispose 模式的诅咒,该模式声明对象必须容忍对Dispose()
的多次显式调用。 (请参阅http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx,&#34; ...允许多次调用Dispose(bool)
方法。该方法可能会在第一次调用后选择不执行任何操作。& #34)
随着 inversion-of-control 的出现,这种糟糕的实现成为一个真正的问题。 I.O.C.容器(特别是Ninject)检测IDisposable
接口,并在注入范围结束时在激活的实例上显式调用Dispose()
。
解决方案:代理ClientBase和Intercept调用Dispose()
我的解决方案是通过继承ClientBase
代理System.Runtime.Remoting.Proxies.RealProxy
并劫持或拦截对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();
...问题在暂存环境中的所有测试设置和负载下都停止了!
为什么吗
任何人都可以解释可能发生了什么以及为什么在调用Channel
之前明确关闭Dispose()
解决了它?据我所知,这不是必要的。
最后,我回到开头的问题:处理WCF代理的正确方法是什么?我替换Dispose()
是否足够?
答案 0 :(得分:0)
据我所知,问题在于调用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;
}
}
}