如何处理WCF异常(附有代码的综合列表)

49
我试图扩展这个SO答案,使WCF客户端在瞬态网络故障时重试并处理其他需要重试的情况,例如身份验证过期。 问题:
需要处理哪些WCF异常,以及正确的处理方式是什么?
以下是我希望看到的几种示例技术,而不是或除了proxy.abort()
- 在重试之前延迟X秒 - 关闭并重新创建New() WCF客户端。 释放旧的。 - 不要重试并重新抛出此错误 - 重试N次,然后抛出
由于一个人不太可能知道所有异常或解决方法,因此请分享您所知道的内容。 我将在下面的代码示例中汇总答案和方法。
    // USAGE SAMPLE
    //int newOrderId = 0; // need a value for definite assignment
    //Service<IOrderService>.Use(orderService=>
    //{
    //  newOrderId = orderService.PlaceOrder(request);
    //}




    /// <summary>
    /// A safe WCF Proxy suitable when sessionmode=false
    /// </summary>
    /// <param name="codeBlock"></param>
    public static void Use(UseServiceDelegateVoid<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;
        try
        {
            codeBlock((T)proxy);
            proxy.Close();
            success = true;
        }
        catch (CommunicationObjectAbortedException e)
        {
                // Object should be discarded if this is reached.  
                // Debugging discovered the following exception here:
                // "Connection can not be established because it has been aborted" 
            throw e;
        }
        catch (CommunicationObjectFaultedException e)
        {
            throw e;
        }
        catch (MessageSecurityException e)
        {
            throw e;
        }
        catch (ChannelTerminatedException)
        {
            proxy.Abort(); // Possibly retry?
        }
        catch (ServerTooBusyException)
        {
            proxy.Abort(); // Possibly retry?
        }
        catch (EndpointNotFoundException)
        {
            proxy.Abort(); // Possibly retry?
        }
        catch (FaultException)
        {
            proxy.Abort();
        }
        catch (CommunicationException)
        {
            proxy.Abort();
        }
        catch (TimeoutException)
        {
         // Sample error found during debug: 

         // The message could not be transferred within the allotted timeout of 
         //  00:01:00. There was no space available in the reliable channel's 
         //  transfer window. The time allotted to this operation may have been a 
         //  portion of a longer timeout.

            proxy.Abort();
        }
        catch (ObjectDisposedException )
        {
            //todo:  handle this duplex callback exception.  Occurs when client disappears.  
            // Source: https://dev59.com/dnM_5IYBdhLWcg3wUxZB#1428238
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

2
拜托了,请在那些catch块中去掉throw e中的e。它会抛弃前面的整个堆栈跟踪,使得逻辑故障排除变成猜谜游戏。 - StingyJack
4个回答

23

编辑:关闭和重新打开客户端多次似乎存在一些低效性。我正在探索解决方案,如果找到一个解决方案,我将更新并扩展此代码。(或者如果David Khaykin发布答案,我会标记它为已接受的)

在尝试了几年之后,以下代码是我首选的策略(在看到这篇来自网络档案馆的博客文章之后)用于处理WCF重试和异常处理。

我调查了每个异常,想知道如何处理该异常,并注意到一个共同点;每个需要“重试”的异常都继承自一个公共基类。我还注意到,每个使客户端处于无效状态的permFail异常也来自于一个共享的基类。

以下示例可以捕获客户端可能抛出的每个WCF异常,并且可扩展以用于您自己的自定义通道错误。

示例WCF客户端使用

生成客户端代理后,您只需实现以下内容即可。

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

ServiceDelegate.cs

将此文件添加到您的解决方案中。无需更改此文件,除非您想要更改重试次数或处理哪些异常。

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 = null;
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           // Proxy cann't be reused
           proxy = (IClientChannel)_channelFactory.CreateChannel();

           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }
           catch (FaultException customFaultEx)
           {
               mostRecentEx = customFaultEx;
               proxy.Abort();

               //  Custom resolution for this app-level exception
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // 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 e)
           {
                // 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 e;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

2
一般来说,我一直在使用.NET 4取消令牌来为我的代码设置超时,但是还没有将其连接到WCF。MSDN上有一个很好的取消令牌示例。 - makerofthings7
修改了代码中的一行,将 "if (mostRecentEx != null)" 改为 "if (success == false && mostRecentEx != null)",现在代码运行完美。 - MüllerDK
1
@themakerofthings7,顺序很重要:在我的理解中,我们首先必须捕获FaultException(因为这是与逻辑相关的),然后捕获CommunicationException作为重试的原因,然后是TimeoutException,最后是Excpetion以捕获所有剩余的异常。除非我漏掉了其他情况。 - port443
1
为什么ChannelFactory<T>没有被处理?这不再必要了吗,还是它只是不在示例代码中的一部分? - Herman Cordes
1
我们看到了另一个WCF异常,ProtocolException。当另一端的服务停止正确协商HTTPS但仍然连接时,就会出现这种情况。 - Chris Moschini
显示剩余10条评论

4
我在 Codeplex 上开始了一个项目,它具有以下功能:
  • 允许客户端代理的高效复用
  • 清理所有资源,包括 EventHandlers
  • 在双工通道上运行
  • 在每次调用服务时运行
  • 支持配置构造函数或工厂

http://smartwcfclient.codeplex.com/

这是一个正在进行中的项目,并且有非常详细的注释。我将感激任何关于改进它的反馈。

在实例模式下的示例用法:

 var reusableSW = new LC.Utils.WCF.ServiceWrapper<IProcessDataDuplex>(channelFactory);

 reusableSW.Reuse(client =>
                      {
                          client.CheckIn(count.ToString());
                      });


 reusableSW.Dispose();

2

1
+1 - 非常好的链接。您能否协助(通过编辑您的答案)提供一个文本描述,说明我如何使用IExceptionToFaultConverter或其他类来实际实现针对超时等问题的WCF纠正行为? - makerofthings7

2
我们有一个WCF客户端,可以处理服务器上几乎任何类型的故障。捕获列表很长,但不必如此。如果你仔细看,你会发现许多异常是Exception类(和其他一些类)的子定义。
因此,如果您愿意,您可以大大简化事情。话虽如此,这里是我们捕获的一些典型错误:
服务器超时 服务器过于繁忙 服务器不可用。

这可能有点老了,但如果我没记错的话,在那个项目中我们创建了自己的异常类,它是异常类的子类。 - Frode Stenstrøm
抱歉,不行。这已经超过6年了。 - Frode Stenstrøm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接