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

我喜欢在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()方法,都会覆盖客户端的Dispose()方法。 就像是:

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

这看起来能够再次允许using块,而没有掩盖故障状态异常的危险。

那么,是否还有其他一些问题需要注意使用这些解决方法? 有人提出任何更好的?


其实,虽然我博客(见卢克的回答),但我认为这比我的IDisposable包装好。 典型代码:

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

(编辑每个评论)

由于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

鉴于IServiceOriented.com倡导的解决方案与David Barret的博客倡导的解决方案之间的选择,我更喜欢覆盖客户端的Dispose()方法所提供的简单性。 这使我可以继续使用using()语句,就像使用一次性对象所期望的那样。 然而,正如@布里安指出的那样,这个解决方案包含一个竞争条件,即状态在被检查时可能没有错误,但可能在调用Close()的时候,在这种情况下仍然发生CommunicationException。

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

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

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

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”方法助手,我们可以按照我们想要的方式配置渠道工厂等。我们也不一定会使用生成的代理 - 任何接口都会执行。

链接地址: http://www.djcxy.com/p/55803.html

上一篇: What is the best workaround for the WCF client `using` block issue?

下一篇: MSMQ, WCF, and Flaky Servers