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

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

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

在MSDN文章中建议的解决方法是完全避免使用using块,而是实例化你的客户端,并像这样使用它们:

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

与using块相比,我认为它很难看。每次需要客户端时都要写大量代码。

幸运的是,我找到了一些其他的解决方法,比如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(); 
            } 
        } 
     } 
} 

这就允许:

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

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

我目前正在尝试使用的解决方法是我第一次在blog.davidbarret.net上读到的。基本上,无论在哪里使用客户机的Dispose()方法,都要重写它。喜欢的东西:

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

这似乎能够允许using块再次出现,而不会有隐藏故障状态异常的危险。

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


当前回答

我终于找到了一些明确解决这个问题的坚实步骤。

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.

Codeplex有一个名为异常处理WCF代理生成器的项目。它基本上是在Visual Studio 2008中安装一个新的自定义工具,然后使用该工具生成新的服务代理(添加服务引用)。它有一些很好的功能来处理故障通道、超时和安全处理。这里有一个名为ExceptionHandlingProxyWrapper的优秀视频,详细解释了这是如何工作的。

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

其他回答

我写了一个高阶函数来让它正确工作。我们已经在几个项目中使用了这个方法,看起来效果不错。这就是从一开始就应该做的事情,没有“使用”范式等等。

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

你可以这样打电话:

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

这和你的例子很像。在一些项目中,我们编写强类型的帮助器方法,因此我们最终会写出类似“Wcf.UseFooService(f=>f…)”这样的东西。

从各方面考虑,我觉得它相当优雅。您遇到过什么特别的问题吗?

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

我的方法是创建一个显式实现IDisposable的继承类。这对于使用gui添加服务引用(添加服务引用)的人很有用。我只是在项目中删除这个类,使服务引用,并使用它而不是默认客户端:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

注意:这只是dispose的一个简单实现,如果你喜欢,你可以实现更复杂的dispose逻辑。

然后你可以用安全客户端替换常规服务客户端的所有调用,如下所示:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

我喜欢这个解决方案,因为它不需要我访问接口定义,我可以使用using语句,因为我期望,同时允许我的代码看起来或多或少相同。

您仍然需要处理可以抛出的异常,正如在本线程的其他注释中指出的那样。

使用扩展方法:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

我有我自己的包装器的通道,实现Dispose如下:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

这似乎工作得很好,并允许使用using块。

我们的系统架构经常使用Unity IoC框架来创建ClientBase实例,所以没有确定的方法来强制其他开发人员使用{}块。为了使它尽可能的万无一难,我做了这个扩展ClientBase的自定义类,并处理在dispose时关闭通道,或者在finalize时关闭通道,以防有人没有显式地处置Unity创建的实例。

还有一些东西需要在构造函数中完成,为自定义凭证和其他东西设置通道,所以也在这里…

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

然后客户可以简单地:

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

调用者可以做以下任何一件事:

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