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

8

我一直在苦恼于WCF代理的问题。正确的方式是什么来Dispose一个WCF代理呢?答案并不简单。

System.ServiceModel.ClientBase违反了微软自己的Dispose模式

System.ServiceModel.ClientBase<TChannel> 确实实现了IDisposable,因此可以假设应该处理它或者在using-块中使用。对于任何可处理事务,这都是最佳实践。但是,它的实现是显式的,因此必须显式地将ClientBase 实例转换为IDisposable,从而使问题复杂化。

然而,最令人困惑的是,调用已故障的ClientBase 实例上的Dispose(),甚至是从未打开过的通道,将导致抛出异常。这不可避免地意味着有意义的解释故障的异常会立即丢失,当堆栈展开时,using 范围结束,并且Dispose() 抛出一个无意义的异常,说您不能处置一个故障通道。

上述行为违反了处理模式,该模式指出对象必须容忍对Dispose()进行多次显式调用。 (参见http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx,“...允许多次调用Dispose(bool)方法。第一次调用后,该方法可能选择不做任何事情。”)

随着控制反转的出现,这种差劲的实现变成了一个真正的问题。I.O.C.容器(特别是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的通道在未故障的情况下无法正确关闭,即使在_client上调用了Dispose方法。

我的ASP.NET MVC应用程序使用我的代理实现连接到本地网络上运行的使用NetTcpBinding的WCF服务,该服务托管在一个只有一个节点的服务集群内的Windows NT服务中。当我对MVC应用程序进行负载测试时,WCF服务上的某些端点(使用端口共享)会在一段时间后停止响应。

我努力尝试重现这个问题:在两台开发人员机器之间运行的相同组件完美运行;使用许多进程和每个进程中的许多线程在真正的WCF端点(运行在暂存服务集群上)上测试控制台应用程序也能正常工作;将MVC应用程序配置在暂存服务器上以连接到开发人员机器上的端点,在负载下也能正常工作;在开发人员机器上运行MVC应用程序并连接到暂存WCF端点也能正常工作。然而,最后一种情况只在IIS Express下才有效,这是一个突破。当使用完整版的IIS在开发人员机器上连接到暂存服务集群时,端点会停止响应。

解决方案:关闭通道

在无法理解问题并阅读了许多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就可以解决这个问题吗?据我所知,这应该是不必要的。

最后,回到开头的问题:正确的WCF代理释放方式是什么?我的Dispose()替代方案是否足够?

1个回答

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接