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

给你一囗甜甜゛ 提交于 2020-02-25 22:14:17

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

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

但是,正如本MSDN文章中所述 ,在using块中包装WCF客户端可能会掩盖导致客户端处于故障状态的任何错误(如超时或通信问题)。 简而言之,当调用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块而没有掩盖故障状态异常的危险。

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


#1楼

根据Marc Gravell,MichaelGG和Matt Davis的回答,我们的开发人员提出了以下建议:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

使用示例:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

它尽可能接近“using”语法,在调用void方法时不必返回虚值,并且可以多次调用服务(并返回多个值)而不必使用元组。

此外,如果需要,您可以使用ClientBase<T>后代而不是ChannelFactory。

如果开发人员想要手动处理代理/通道,则会暴露扩展方法。


#2楼

考虑到IServiceOriented.com倡导的解决方案与David Barret博客倡导的解决方案之间的选择,我更喜欢通过覆盖客户端的Dispose()方法提供的简单性。 这允许我继续使用using()声明,就像一个一次性对象所期望的那样。 但是,正如@Brian指出的那样,这个解决方案包含一个竞争条件,因为状态在检查时可能不会出现故障,但可能在调用Close()时,在这种情况下仍然会发生CommunicationException。

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

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

#3楼

我写了一个简单的基类来处理这个问题。 它可以作为NuGet包使用 ,而且非常易于使用。

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}

#4楼

@Marc Gravell

使用它不是没关系的:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

或者,在Service<IOrderService>.Use情况下,同样的事情(Func<T, TResult>)

这些将使返回变量更容易。


#5楼

public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

所以它允许很好地编写return语句:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!