C# WCF 客户端“使用”块问题的最佳解决方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/573872/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 08:49:04  来源:igfitidea点击:

What is the best workaround for the WCF client `using` block issue?

c#vb.netwcfusingwcf-client

提问by Eric King

I like instantiating my WCF service clients within a usingblock as it's pretty much the standard way to use resources that implement IDisposable:

我喜欢在一个using块中实例化我的 WCF 服务客户端,因为它几乎是使用实现IDisposable以下资源的标准方式:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

But, as noted in this MSDN article, wrapping a WCF client in a usingblock could mask any errors that result in the client being left in a faulted state (like a timeout or communication problem). Long story short, when Dispose() is called, the client's Close() method fires, but throws an error because it's in a faulted state. The original exception is then masked by the second exception. Not good.

但是,正如这篇 MSDN 文章中所述,将 WCF 客户端包装在一个using块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。长话短说,当调用 Dispose() 时,客户端的 Close() 方法会触发,但会抛出错误,因为它处于错误状态。然后原始异常被第二个异常屏蔽。不好。

The suggested workaround in the MSDN article is to completely avoid using a usingblock, and to instead instantiate your clients and use them something like this:

MSDN 文章中建议的解决方法是完全避免使用using块,而是实例化您的客户端并使用它们,如下所示:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Compared to the usingblock, I think that's ugly. And a lot of code to write each time you need a client.

using方块相比,我认为那是丑陋的。每次需要客户端时都要编写大量代码。

Luckily, I found a few other workarounds, such as this one on IServiceOriented. You start with:

幸运的是,我找到了其他一些解决方法,例如 IServiceOriented 上的这个。你开始:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Which then allows:

然后允许:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

That's not bad, but I don't think it's as expressive and easily understandable as the usingblock.

这还不错,但我认为它不像using块那样具有表现力和易于理解。

The workaround I'm currently trying to use I first read about on blog.davidbarret.net. Basically you override the client's Dispose()method wherever you use it. Something like:

我目前尝试使用的解决方法是我第一次在blog.davidbarret.net读到的。基本上,Dispose()无论您在哪里使用它,您都可以覆盖客户端的方法。就像是:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

This appears to be able to allow the usingblock again without the danger of masking a faulted state exception.

这似乎能够using再次允许阻止,而不会掩盖故障状态异常的危险。

So, are there any other gotchas I have to look out for using these workarounds? Has anybody come up with anything better?

那么,在使用这些变通方法时,我还需要注意其他问题吗?有没有人想出更好的办法?

采纳答案by Marc Gravell

Actually, although I blogged(see Luke's answer), I think thisis better than my IDisposable wrapper. Typical code:

实际上,虽然我写了博客(参见Luke 的回答),但我认为比我的 IDisposable 包装器更好。典型代码:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 


(edit per comments)

(根据评论编辑)

Since Usereturns void, the easiest way to handle return values is via a captured variable:

由于Use返回 void,处理返回值的最简单方法是通过捕获的变量:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

回答by MichaelGG

I wrote a higher order functionto make it work right. We've used this in several projects and it seems to work great. This is how things should have been done from the start, without the "using" paradigm or so on.

我写了一个高阶函数来使它正常工作。我们已经在几个项目中使用了它,它似乎工作得很好。这就是从一开始就应该做的事情,没有“使用”范式等等。

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

You can make calls like this:

您可以这样拨打电话:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

This is pretty much just like you have in your example. In some projects, we write strongly typed helper methods, so we end up writing things like "Wcf.UseFooService(f=>f...)".

这与您在示例中的情况非常相似。在某些项目中,我们编写了强类型的辅助方法,因此我们最终会编写诸如“Wcf.UseFooService(f=>f...)”之类的东西。

I find it quite elegant, all things considered. Is there a particular problem you encountered?

考虑到所有因素,我觉得它非常优雅。你遇到过什么特别的问题吗?

This allows other nifty features to be plugged in. For instance, on one site, the site authenticates to the service on behalf of the logged in user. (The site has no credentials by itself.) By writing our own "UseService" method helper, we can configure the channel factory the way we want, etc. We're also not bound to using the generated proxies -- any interface will do.

这允许插入其他漂亮的功能。例如,在一个站点上,该站点代表登录用户对服务进行身份验证。(该站点本身没有凭据。)通过编写我们自己的“UseService”方法助手,我们可以按照我们想要的方式配置通道工厂,等等。我们也不必使用生成的代理——任何接口都可以.

回答by CodingWithSpike

Our system architecture often uses the UnityIoCframework to create instances of ClientBase so there's no sure way to enforce that the other developers even use using{}blocks. In order to make it as fool-proof as possible, I made this custom class that extends ClientBase, and handles closing down the channel on dispose, or on finalize in case someone doesn't explicitly dispose of the Unity created instance.

我们的系统架构经常使用Unity IoC框架来创建 ClientBase 的实例,因此没有确定的方法来强制其他开发人员甚至使用using{}块。为了尽可能做到万无一失,我制作了这个扩展 ClientBase 的自定义类,并在处置时处理关闭通道,或者在有人没有明确处置 Unity 创建的实例时处理关闭通道。

There is also stuff that needed to be done in the constructor to set up the channel for custom credentials and stuff, so that's in here too...

还需要在构造函数中完成一些工作来为自定义凭据和内容设置通道,所以这也在这里......

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Then a client can simply:

然后客户可以简单地:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

And the caller can do any of these:

调用者可以执行以下任何操作:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}

回答by Matt Davis

Given a choice between the solution advocated by IServiceOriented.com and the solution advocated by David Barret's blog, I prefer the simplicity offered by overriding the client's Dispose() method. This allows me to continue to use the using() statement as one would expect with a disposable object. However, as @Brian pointed out, this solution contains a race condition in that the State might not be faulted when it is checked but could be by the time Close() is called, in which case the CommunicationException still occurs.

如果在 IServiceOriented.com 提倡的解决方案和David Barret 的博客提倡的解决方案之间进行选择,我更喜欢通过覆盖客户端的 Dispose() 方法提供的简单性。这允许我继续使用 using() 语句,就像人们对一次性对象所期望的那样。但是,正如@Brian 指出的那样,此解决方案包含一个竞争条件,即 State 在检查时可能不会出错,但在调用 Close() 时可能会出错,在这种情况下,仍然会发生 CommunicationException。

So, to get around this, I've employed a solution that mixes the best of both worlds.

因此,为了解决这个问题,我采用了一种混合了两全其美的解决方案。

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

回答by Neil

I've finally found some solid steps towards a clean solution to this problem.

我终于找到了一些可靠的步骤来解决这个问题。

This custom tool extends WCFProxyGenerator to provide an exception handling proxy. It generates an additional proxy called ExceptionHandlingProxy<T>which inherits ExceptionHandlingProxyBase<T>- the latter of which implements the meat of the proxy's functionality. The result is that you can choose to use the default proxy that inherits ClientBase<T>or ExceptionHandlingProxy<T>which encapsulates managing the lifetime of the channel factory and channel. ExceptionHandlingProxy respects your selections in the Add Service Reference dialog with respect to asynchronous methods and collection types.

此自定义工具扩展 WCFProxyGenerator 以提供异常处理代理。它生成一个额外的代理,称为ExceptionHandlingProxy<T>继承ExceptionHandlingProxyBase<T>——后者实现了代理功能的核心。结果是您可以选择使用继承ClientBase<T>ExceptionHandlingProxy<T>封装管理通道工厂和通道生命周期的默认代理。ExceptionHandlingProxy 尊重您在“添加服务引用”对话框中关于异步方法和集合类型的选择。

Codeplexhas a project called Exception Handling WCF Proxy Generator. It basically installs a new custom tool to Visual Studio 2008, then use this tool to generate the new service proxy (Add service reference). It has some nice functionality to deal with faulted channels, timeouts and safe disposal. There's an excellent video here called ExceptionHandlingProxyWrapperexplaining exactly how this works.

Codeplex有一个名为Exception Handling WCF Proxy Generator的项目。它基本上是安装一个新的自定义工具到 Visual Studio 2008,然后使用这个工具生成新的服务代理(添加服务引用)。它有一些很好的功能来处理故障通道、超时和安全处理。这里有一个很棒的视频叫做ExceptionHandlingProxyWrapper解释了它是如何工作的。

You can safely use the Usingstatement again, and if the channel is faulted on any request (TimeoutException or CommunicationException), the Wrapper will re-initialize the faulted channel and retry the query. If that fails then it will call the Abort()command and dispose of the proxy and rethrow the Exception. If the service throws a FaultExceptioncode it will stop executing, and the proxy will be aborted safely throwing the correct exception as expected.

您可以安全地Using再次使用该语句,并且如果通道在任何请求(TimeoutException 或 CommunicationException)上出现故障,Wrapper 将重新初始化故障通道并重试查询。如果失败,那么它将调用Abort()命令并处理代理并重新抛出异常。如果服务抛出一个FaultException代码,它将停止执行,并且代理将被安全地中止,并按预期抛出正确的异常。

回答by Tomas Jansson

A wrapper like this would work:

像这样的包装器会起作用:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

That should enable you to write code like:

这应该使您能够编写如下代码:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

The wrapper could of course catch more exceptions if that is required, but the principle remains the same.

如果需要,包装器当然可以捕获更多异常,但原理保持不变。

回答by goodguys_activate

This is Microsoft's recommended way to handle WCF client calls:

这是 Microsoft 推荐的处理 WCF 客户端调用的方法:

For more detail see: Expected Exceptions

有关更多详细信息,请参阅:预期异常

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

Additional informationSo many people seem to be asking this question on WCF that Microsoft even created a dedicated sample to demonstrate how to handle exceptions:

附加信息似乎有很多人在 WCF 上问这个问题,以至于微软甚至创建了一个专门的示例来演示如何处理异常:

c:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\client

c:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\client

Download the sample: C#or VB

下载示例: C#VB

Considering that there are so many issues involving the using statement, (heated?) Internal discussionsand threadson this issue, I'm not going to waste my time trying to become a code cowboy and find a cleaner way. I'll just suck it up, and implement WCF clients this verbose (yet trusted) way for my server applications.

考虑到涉及 using 语句的问题如此之多,(热?)关于这个问题的内部讨论线程,我不会浪费时间试图成为一个代码牛仔并找到一种更清洁的方法。我会接受它,并为我的服务器应用程序以这种冗长(但值得信赖)的方式实现 WCF 客户端。

Optional Additional Failures to catch

要捕获的可选附加故障

Many exceptions derive from CommunicationExceptionand I don't think most of those exceptions should be retried. I drudged through each exception on MSDN and found a short list of retry-able exceptions (in addition to TimeOutExceptionabove). Do let me know if I missed an exception that should be retried.

许多异常源自CommunicationException,我认为不应重试其中的大多数异常。我仔细研究了 MSDN 上的每个异常,并找到了一个可重试异常的简短列表(除TimeOutException上述之外)。如果我错过了应该重试的异常,请告诉我。

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

Admittedly, this is a bit of mundane code to write. I currently prefer this answer, and don't see any "hacks" in that code that may cause issues down the road.

诚然,这是一个有点平凡的代码编写。我目前更喜欢这个答案,并且在该代码中看不到任何可能导致问题的“黑客”。

回答by Jesse C. Slicer

Below is an enhanced version of the source from the questionand extended to cache multiple channel factories and attempt to look up the endpoint in the configuration file by contract name.

下面是问题源的增强版本,并扩展到缓存多个通道工厂,并尝试按合同名称在配置文件中查找端点。

It uses .NET 4 (specifically: contravariance, LINQ, var):

它使用 .NET 4(特别是:逆变、LINQ、var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

回答by Luiz Felipe

If you don't need IoCor are using an autogenerated client (Service Reference), then you can simple use a wrapper to manage the closing and let the GCtake the clientbase when it is in a safe state that will not throw any exception. The GC will call Dispose in serviceclient, and this will call Close. Since it is alread closed, it cannot cause any damage. I am using this without problems in production code.

如果您不需要IoC或正在使用自动生成的客户端(服务引用),那么您可以简单地使用包装器来管理关闭,并让GC在客户端处于安全状态时不会抛出任何异常。GC 将在 serviceclient 中调用 Dispose,这将调用Close. 由于它已经关闭,因此不会造成任何损坏。我在生产代码中使用它没有问题。

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

Then when you are accessing the server, you create the client and use usingin the autodisconect:

然后在访问服务器时,创建客户端并using在 autodisconect 中使用:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}

回答by Jay Douglass

I used Castle dynamic proxy to solve the Dispose() issue, and also implemented auto-refreshing the channel when it is in an unusable state. To use this you must create a new interface that inherits your service contract and IDisposable. The dynamic proxy implements this interface and wraps a WCF channel:

我使用 Castle 动态代理来解决 Dispose() 问题,并在通道处于不可用状态时实现了自动刷新通道。要使用它,您必须创建一个继承您的服务合同和 IDisposable 的新接口。动态代理实现了这个接口并封装了一个 WCF 通道:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

I like this since you can inject WCF services without consumers needing to worry about any details of WCF. And there's no added cruft like the other solutions.

我喜欢这样,因为您可以注入 WCF 服务,而消费者无需担心 WCF 的任何细节。并且没有像其他解决方案那样添加多余的东西。

Have a look at the code, it's actually pretty simple: WCF Dynamic Proxy

看一下代码,其实很简单: WCF Dynamic Proxy