65.9K
CodeProject 正在变化。 阅读更多。
Home

不要在 WCF 客户端中使用 "using"

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (25投票s)

2011 年 5 月 17 日

CPOL

1分钟阅读

viewsIcon

119435

传统的 `using()` 块在出现通信异常时(例如,网络连接中断)会错误地释放 WCF 客户端。 它会在释放时引发异常,从而导致 WCF 客户端持有的资源无法正确释放。 经过一段时间后,最终会导致内存泄漏。

您知道任何 `IDisposable` 对象都必须使用 `using` 释放。 因此,您一直使用 `using` 来包装 WCF 服务的 `ChannelFactory` 和客户端,就像这样:
using(var client = new SomeClient()) {
. 
.
.
}
或者,如果您正在以困难且缓慢的方式执行此操作(实际上并不知道原因),那么:
using(var factory = new ChannelFactory<ISomeService>()) {
var channel= factory.CreateChannel();
.
.
.
}
这就是我们都在学校里学到的,对吧? 我们学错了! 当发生网络相关错误、连接中断或在调用 `Dispose` 之前调用超时时,`using` 关键字尝试释放通道时,就会导致以下异常:
failed: System.ServiceModel.CommunicationObjectFaultedException : 
The communication object, System.ServiceModel.Channels.ServiceChannel, 
cannot be used for communication because it is in the Faulted state.
    
    Server stack trace: 
    at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
    
    Exception rethrown at [0]: 
    at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
    at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
    at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
    at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
    at System.ServiceModel.ClientBase`1.Close()
    at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
导致底层连接处于中断状态的原因有很多,在 `using` 块完成并且调用 `Dispose()` 之前。 常见问题包括网络连接中断、IIS 在此时进行应用程序池回收、某些代理位于您和该服务之间并由于各种原因中断连接等等。 关键在于,这可能看起来是一个边缘情况,但它是一个可能发生的边缘情况。 如果您正在构建一个高可用性客户端,您需要在上线之前正确处理此问题。 因此,不要在 WCF `Channel/Client/ChannelFactory` 上使用 `using`。 相反,您需要使用替代方法。 首先创建一个扩展方法:
public static class WcfExtensions
{
    public static void Using<T>(this T client, Action<T> work)
        where T : ICommunicationObject
    {
        try
        {
            work(client);
            client.Close();
        }
        catch (CommunicationException e)
        {
            client.Abort();
        }
        catch (TimeoutException e)
        {
            client.Abort();
        }
        catch (Exception e)
        {
            client.Abort();
            throw;
        }
    }
}
然后使用此方法代替 `using` 关键字:
new SomeClient().Using(channel => {
    channel.Login(username, password);
});
或者,如果您正在使用 `ChannelFactory`,那么:
new ChannelFactory<ISomeService>().Using(channel => {    
    channel.Login(username, password);
});
尽情享用!
© . All rights reserved.