处理WCF代理的正确方法是什么?

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()足够?

Uma*_*aja 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)