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






4.83/5 (25投票s)
传统的 `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); });尽情享用!