当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《WCF客户端"使用"块问题的最佳解决方法是什么?》经验,为你挑选了10个好方法。

我喜欢在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 proxy); 

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

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

然后允许:

Service.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..:

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

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

(根据评论编辑)

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

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


您可以轻松修改包装器,因此您不需要结果的捕获变量.像这样:`public static TResult使用(Func codeBlock){...}`
也许有用`https:// devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-proper /`和`https://devzone.channeladam.com/articles/2014/09/如何轻松调用-wcf-service-proper /`和`http:// dzimchuk.net/post/wcf-error-helpers`
@MarcGravell我可以在哪里注入该客户端?我假设ChannelFactory创建了客户端,并且在Service类中新建了工厂对象,这意味着代码应该被重构一点以允许自定义工厂.这是正确的,还是我错过了一些明显的东西?
在我看来,最正确的解决方案是:***1)在没有竞争条件的情况下执行关闭/中止模式2)处理服务操作抛出异常时的情况3)处理Close和Abort方法抛出异常时的情况4)处理异步异常,例如ThreadAbortException***`https:// devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-proper /`

2> Matt Davis..:

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


@MattDavis为什么你需要"成功"旗帜?为什么不`try {Close(); } catch {Abort(); 扔; }`?
在非托管资源中使用'Try-Finally'(或语法糖 - "using(){}")语句不是很危险吗?例如,如果"关闭"选项失败,则不会捕获异常,最终可能无法运行.此外,如果finally语句中存在异常,则可以屏蔽其他异常.我认为这就是为什么Try-Catch是首选的原因.

3> MichaelGG..:

我写了一个更高阶的函数来使它正常工作.我们已经在几个项目中使用了它,它看起来效果很好.这就是从一开始就应该做的事情,没有"使用"范式等等.

TReturn UseService(Func code)
{
    var chanFactory = GetCachedFactory();
    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"方法帮助程序,我们可以按照我们想要的方式配置通道工厂等.我们也不必使用生成的代理 - 任何接口都可以.



4> goodguys_act..:

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

有关更多详细信息,请参阅:预期的例外情况

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

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

C:\ WF_WCF_Samples\WCF \基本\客户端\ ExpectedExceptions\CS \客户端

下载示例: C#或 VB

考虑到涉及使用声明的问题很多,(加热?)内部讨论和线程就此问题,我不会浪费时间试图成为代码牛仔并找到一种更清洁的方式.我只是简单地说,并为我的服务器应用程序实现WCF客户端这种冗长(但可信)的方式.

可选的附加失败事件

许多例外来自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
}

不可否认,这是一些平凡的代码.我目前更喜欢这个答案,并且在该代码中看不到任何可能导致问题的"黑客".



5> Neil..:

我终于找到了解决这个问题的一个坚实步骤.

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

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

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



6> TrueWill..:

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

public static class UsingServiceClient
{
    public static void Do(TClient client, Action 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如果需要,您可以将其与后代而不是ChannelFactory一起使用.

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



7> pangular..:

@Marc Gravell

使用它不是没关系的:

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

        client.Close();

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

        throw;
    }
}

或者,同样的(Func)情况下Service.Use

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


+1 @MarcGravell我想您的回答也“可以做得更好”:P(而且可以用一个具有空返回值的Func来实现动作)。整个页面都是一团糟-如果我想在这十年中的任何时候使用WCF,我都会制定一个统一的页面并评论傻瓜...

8> Jesse C. Sli..:

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

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

/// 
/// Delegate type of the service method to perform.
/// 
/// The service proxy.
/// The type of service to use.
internal delegate void UseServiceDelegate(T proxy);

/// 
/// Wraps using a WCF service.
/// 
/// The type of service to use.
internal static class Service
{
    /// 
    /// A dictionary to hold looked-up endpoint names.
    /// 
    private static readonly IDictionary cachedEndpointNames = new Dictionary();

    /// 
    /// A dictionary to hold created channel factories.
    /// 
    private static readonly IDictionary> cachedFactories =
        new Dictionary>();

    /// 
    /// Uses the specified code block.
    /// 
    /// The code block.
    internal static void Use(UseServiceDelegate 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();
            }
        }
    }

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

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

            var factory = new ChannelFactory(endpointName);

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

    /// 
    /// Gets the name of the endpoint.
    /// 
    /// The name of the endpoint.
    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().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.");
    }
}



9> goodguys_act..:

这是什么?

这是已接受答案的CW版本,但(我认为完整)包括异常处理.

接受的答案引用了不再存在的这个网站.为了省去麻烦,我在这里列出了最相关的部分.此外,我稍微修改它以包括异常重试处理来处理那些讨厌的网络超时.

简单的WCF客户端使用

生成客户端代理后,这就是实现它所需的全部内容.

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

ServiceDelegate.cs

将此文件添加到您的解决方案.除非您想要更改重试次数或要处理的异常,否则不需要对此文件进行任何更改.

public delegate void UseServiceDelegate(T proxy);

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

    public static void Use(UseServiceDelegate codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // 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)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS:我已将此帖发布为社区维基.我不会从这个答案中收集"积分",但如果您同意该实施,或者编辑它以使其更好,则更愿意您赞成它.



10> Tomas Jansso..:

像这样的包装器可以工作:

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

    private static ChannelFactory _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory(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
        }
    }
}

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

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

如果需要,包装器当然可以捕获更多例外,但原理保持不变.

推荐阅读
郑谊099_448
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有