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

时间:2014-12-12 10:14:17

标签: asp.net-mvc wcf dispose nettcpbinding realproxy

我一直在努力使用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()是否足够?

1 个答案:

答案 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;
        }
    }
}