Xha*_*lie 7 asp.net-mvc wcf dispose nettcpbinding realproxy
我一直在努力与WCF Proxies.处理WCF代理的正确方法是什么?答案并非微不足道.
System.ServiceModel.ClientBase违反了Microsoft自己的Dispose-pattern
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和Intercept调用Dispose()
我的解决方案是ClientBase
通过子类化代理System.Runtime.Remoting.Proxies.RealProxy
并劫持或拦截调用Dispose()
.我的第一个替换Dispose()
是这样的:
if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();
Run Code Online (Sandbox Code Playgroud)
(注意,它_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();
Run Code Online (Sandbox Code Playgroud)
...并且问题在暂存环境中的所有测试设置和负载下都停止了!
为什么?
任何人都可以解释可能发生的事情以及为什么明确关闭Channel
之前的调用Dispose()
解决了它?据我所知,这不应该是必要的.
最后,我回到开头问题:处理WCF代理的正确方法是什么?我的替代品是否Dispose()
足够?
据我所知,问题在于调用会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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1287 次 |
最近记录: |