48 votes

Comment gérer les exceptions WCF (liste consolidée avec code)

J'essaie d'étendre cette réponse sur SO pour faire un client de WCF réessayer sur les échecs transitoires de réseau et manipuler d'autres situations qui nécessitent une nouvelle tentative, comme l'expiration de l'authentification.

Question :

Quelles sont les exceptions WCF qui doivent être traitées, et quelle est la manière correcte de les traiter ?

Voici quelques exemples de techniques que j'espère voir remplacer ou compléter proxy.abort() :

  • Délai de X secondes avant la nouvelle tentative
  • Fermez et recréez un client WCF New(). Déposez l'ancien client.
  • Ne pas réessayer et rejeter cette erreur.
  • Réessayez N fois, puis lancez

Comme il est peu probable qu'une seule personne connaisse toutes les exceptions ou les moyens de les résoudre, partagez ce que vous savez. Je vais regrouper les réponses et les approches dans l'exemple de code ci-dessous.

    // 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://stackoverflow.com/questions/1427926/detecting-client-death-in-wcf-duplex-contracts/1428238#1428238
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

21voto

makerofthings7 Points 10028

EDIT : Il semble y avoir quelques inefficacités avec la fermeture et la réouverture du client plusieurs fois. Je suis explorer des solutions ici et mettra à jour et développera ce code si on en trouve un. (ou si David Khaykin poste une réponse, je la marquerai comme acceptée)

Après avoir bricolé pendant quelques années, le code ci-dessous est ma stratégie préférée ( après avoir vu cette publication de blog de la machine wayback ) pour traiter les tentatives WCF et les exceptions.

J'ai étudié chaque exception, ce que je voulais faire avec cette exception, et j'ai remarqué un trait commun : toutes les exceptions qui nécessitaient une "réessai" héritaient d'une classe de base commune. J'ai également remarqué que chaque exception permFail qui mettait le client dans un état invalide provenait également d'une classe de base commune.

L'exemple suivant piège chaque exception WCF qu'un client pourrait traverser, et est extensible pour vos propres erreurs de canal personnalisées.

Exemple d'utilisation du client WCF

Une fois que vous avez généré votre proxy côté client, c'est tout ce dont vous avez besoin pour le mettre en œuvre.

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

ServiceDelegate.cs

Ajoutez ce fichier à votre solution. Aucune modification n'est nécessaire dans ce fichier, sauf si vous souhaitez modifier le nombre de tentatives ou les exceptions à gérer.

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

    }
}

4voto

makerofthings7 Points 10028

J'ai lancé un projet sur Codeplex qui présente les caractéristiques suivantes

  • Permet une réutilisation efficace du proxy client
  • Nettoie toutes les ressources, y compris les gestionnaires d'événements.
  • Fonctionne sur les canaux duplex
  • Fonctionne sur des services à la demande
  • Supporte le constructeur de configuration, ou par usine

http://smartwcfclient.codeplex.com/

Il s'agit d'un travail en cours, et il est très fortement commenté. J'apprécierai tout retour d'information concernant son amélioration.

Exemple d'utilisation en mode instance :

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

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

 reusableSW.Dispose();

2voto

Frode Stenstrøm Points 896

Nous avons un client WCF qui gère presque tous les types de défaillance du serveur. La liste de Catch est très longue mais ne doit pas l'être. Si vous regardez de près, vous verrez que de nombreuses exceptions sont des définitions enfant de la classe Exception (et de quelques autres classes).

Vous pouvez donc simplifier considérablement les choses si vous le souhaitez. Cela dit, voici quelques erreurs typiques que nous attrapons :

Délai d'attente du serveur
Le serveur est trop occupé
Le serveur n'est pas disponible.

2voto

Pradeep Points 601

Les liens ci-dessous peuvent vous aider à gérer les exceptions WCF :

http://www.codeproject.com/KB/WCF/WCFErrorHandling.aspx

http://msdn.microsoft.com/en-us/library/cc949036.aspx

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X