543 votes

C# plus propre façon d'écrire à la logique de nouvelle tentative?

Parfois j'ai besoin de relancer une opération plusieurs fois avant d'abandonner. Mon code est comme:

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

Je voudrais réécrire ce dans un général de réessayer de fonction comme:

TryThreeTimes(DoSomething);

Est-il possible en C#? Quel serait le code pour l' TryThreeTimes() méthode?

645voto

LBushkin Points 60611

Couverture instructions catch simplement réessayer la même appel peut être dangereux s'il est utilisé comme un mécanisme de gestion des exceptions générales. Cela dit, voici un lambda à base de réessayer wrapper que vous pouvez utiliser avec n'importe quelle méthode. J'ai choisi de facteur, le nombre de tentatives et le délai de tentatives comme des paramètres pour un peu plus de souplesse:

public static class Retry
{
   public static void Do(
       Action action,
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       Do<object>(() => 
       {
           action();
           return null;
       }, retryInterval, retryCount);
   }

   public static T Do<T>(
       Func<T> action, 
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       var exceptions = new List<Exception>();

       for (int retry = 0; retry < retryCount; retry++)
       {
          try
          { 
              return action();
          }
          catch (Exception ex)
          { 
              exceptions.Add(ex);
              Thread.Sleep(retryInterval);
          }
       }

       throw new AggregateException(exceptions);
   }
}

Vous pouvez maintenant utiliser cette méthode utilitaire pour effectuer logique de nouvelle tentative:

Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));

ou:

Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));

ou:

int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);

Ou vous pourriez même faire un async de surcharge.

279voto

Michael Wolfenden Points 233

Vous devriez essayer de Polly. Son un .NET bibliothèque écrite par moi que permet aux développeurs d'exprimer transitoire de gestion des exceptions de politiques telles que la nouvelle tentative, nouvelle tentative pour Toujours, Attendre et Réessayer ou Disjoncteur dans un fluide.

https://github.com/michael-wolfenden/Polly

77voto

Drew Noakes Points 69288
public void TryThreeTimes(Action action)
{
    int retries = 3;
    while(true) {
      try {
        action();
        break; // success!
      } catch {
        if(--retries == 0) throw;
        else Thread.Sleep(1000);
      }
   }
}

Ensuite, vous composez le:

TryThreeTimes(DoSomething);

...ou sinon...

TryThreeTimes(() => DoSomethingElse(withLocalVariable));

Un plus souples option:

public void DoWithRetry(Action action, TimeSpan sleepPeriod, int retryCount = 3)
{
    while(true) {
      try {
        action();
        break; // success!
      } catch {
        if(--retryCount == 0) throw;
        else Thread.Sleep(sleepPeriod);
      }
   }
}

Pour être utilisé comme:

DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), retryCount: 10);

57voto

Eric Lippert Points 300275

C'est peut-être une mauvaise idée. Tout d'abord, il est emblématique de la maxime "la définition de la folie est de faire deux fois la même chose et espérer des résultats différents à chaque fois". Deuxièmement, ce modèle de codage n'est pas bien composer avec lui-même. Par exemple:

Supposons que votre matériel réseau couche renvoie un paquet de trois fois en cas d'erreur, d'attente, de dire, d'une seconde entre les pannes.

Supposons maintenant que la couche de logiciel renvoie une notification à propos d'un échec à trois reprises sur le paquet de l'échec.

Supposons maintenant que la notification de la couche réactive la notification à trois reprises sur une notification d'échec de remise.

Supposons maintenant que le rapport d'erreur de la couche réactive la notification de la couche trois fois sur une notification d'échec.

Et maintenant, supposons que le serveur web réactive le rapport d'erreur à trois reprises sur erreur échec.

Et maintenant, imaginons que le client web envoie à nouveau la demande trois fois sur une erreur du serveur.

Supposons maintenant que la ligne sur le commutateur de réseau qui est censé route de la notification de l'administrateur est débranché. Lorsque l'utilisateur du client web enfin à leur message d'erreur? Je le fais à environ douze minutes plus tard.

De peur que vous pensez que c'est juste un exemple stupide: nous avons vu ce bug dans le code client, bien loin, bien pire que ce que j'ai décrit ici. Dans le code du client, l'écart entre la condition d'erreur qui se passe et enfin de les signaler à l'utilisateur a plusieurs semaines , car de nombreuses couches ont été automatiquement une nouvelle tentative avec attend. Imaginez ce qui se passerait s'il y avait dix tentatives au lieu de trois.

Généralement la bonne chose à faire avec une condition d'erreur est de le signaler immédiatement et permettre à l'utilisateur de décider quoi faire. Si l'utilisateur veut créer une politique de tentatives automatiques, laissez-les créer que de la politique au niveau approprié dans le logiciel de l'abstraction.

34voto

Grigori Melnik Points 2676

Le Transitoire de gestion des Pannes Bloc d'Application fournit un extensible de la collection de réessayer de stratégies, y compris:

  • Différentiels
  • Intervalle fixe
  • L'algorithme de reprise

Il comprend également une collection de détection des erreurs de stratégies pour les services basés sur le cloud.

Pour plus d'informations, voir le présent chapitre du Guide du Développeur.

Disponible via NuGet (recherche de 'topaze').

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