Manière la plus propre d'écrire la logique de nouvelle tentative?

455

Parfois, j'ai besoin de réessayer 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 ceci dans une fonction de nouvelle tentative générale comme:

TryThreeTimes(DoSomething);

Est-ce possible en C #? Quel serait le code de la TryThreeTimes()méthode?

noctonura
la source
1
Un simple cycle ne suffit pas? Pourquoi ne pas simplement répéter et exécuter la logique plusieurs fois?
Restuta
13
Personnellement, je me méfierais énormément d'une telle méthode d'aide. Il est certainement possible d'implémenter à l'aide de lambdas, mais le modèle lui-même est extrêmement malodorant, donc l'introduction d'un assistant (ce qui implique qu'il est fréquemment répété) est en soi très suspecte et fait fortement allusion à une mauvaise conception globale.
Pavel Minaev
12
Dans mon cas, mes DoSomething () effectuent des tâches sur des machines distantes telles que la suppression de fichiers ou la tentative d'accès à un port réseau. Dans les deux cas, il y a des problèmes de timing majeurs pour le moment où DoSomething réussira et en raison de l'éloignement, il n'y a aucun événement sur lequel je puisse écouter. Alors oui, c'est malodorant. Suggestions bienvenues.
noctonura
13
@PavelMinaev pourquoi l'utilisation de nouvelles tentatives ferait-elle allusion à une mauvaise conception globale? Si vous écrivez beaucoup de code qui connecte des points d'intégration, l'utilisation de nouvelles tentatives est certainement un modèle que vous devriez sérieusement envisager d'utiliser.
bytedev

Réponses:

569

Les instructions catch de couverture qui relancent simplement le même appel peuvent être dangereuses si elles sont utilisées comme mécanisme général de gestion des exceptions. Cela dit, voici un wrapper de nouvelle tentative basé sur lambda que vous pouvez utiliser avec n'importe quelle méthode. J'ai choisi de prendre en compte le nombre de tentatives et le délai d'expiration des tentatives comme paramètres pour un peu plus de flexibilité:

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

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

        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    Thread.Sleep(retryInterval);
                }
                return action();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }
}

Vous pouvez maintenant utiliser cette méthode utilitaire pour exécuter la 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 une asyncsurcharge.

LBushkin
la source
7
+1, en particulier pour l'avertissement et la vérification des erreurs. Je serais plus à l'aise si cela passait dans le type de l'exception à attraper en tant que paramètre générique (où T: Exception), cependant.
TrueWill
1
J'avais l'intention que "réessayer" signifiait en fait des réessais. Mais il n'est pas trop difficile de le changer pour signifier "essais". Tant que le nom a un sens. Il existe d'autres possibilités d'améliorer le code, comme la vérification des tentatives négatives ou des délais d'expiration négatifs, par exemple. J'ai omis ceux-ci principalement pour garder l'exemple simple ... mais encore une fois, dans la pratique, ce seraient probablement de bonnes améliorations à la mise en œuvre.
LBushkin
40
Nous utilisons un modèle similaire pour notre accès à la base de données dans une application Biztalk à volume élevé, mais avec deux améliorations: nous avons des listes noires pour les exceptions qui ne doivent pas être réessayées et nous stockons la première exception qui se produit et la levons lorsque la nouvelle tentative échoue finalement. La raison étant que la deuxième exception et les exceptions suivantes sont souvent différentes de la première. Dans ce cas, vous masquez le problème initial lorsque vous ne renvoyez que la dernière exception.
TToni
2
@Dexters Nous lançons une nouvelle exception avec l'exception d'origine comme exception interne. La trace de pile d'origine est disponible en tant qu'attribut à partir des exceptions internes.
TToni
7
Vous pouvez également essayer d'utiliser une bibliothèque open source telle que Polly pour gérer cela. Il y a beaucoup plus de flexibilité pour attendre entre les tentatives et il a été validé par de nombreux autres qui ont utilisé le projet. Exemple: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen
222

Vous devriez essayer Polly . C'est une bibliothèque .NET écrite par moi qui permet aux développeurs d'exprimer de manière fluide des politiques de gestion d'exceptions transitoires telles que Retry, Retry Forever, Wait and Retry ou Circuit Breaker.

Exemple

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());
Michael Wolfenden
la source
1
Qu'est-ce que le délégué OnRetry? Je suppose que c'est ce que nous devons effectuer lorsqu'une exception se produit. Ainsi, lorsqu'une exception se produit, le délégué OnRetry appelle et ensuite exécute le délégué. En est-il ainsi?
user6395764
61

C'est peut-être une mauvaise idée. Premièrement, elle est emblématique de la maxime "la définition de la folie fait deux fois la même chose et attend des résultats différents à chaque fois". Deuxièmement, ce modèle de codage ne se compose pas bien avec lui-même. Par exemple:

Supposons que votre couche matérielle réseau renvoie un paquet trois fois en cas d'échec, en attendant, disons, une seconde entre les échecs.

Supposons maintenant que la couche logicielle renvoie trois fois une notification d'échec lors d'une défaillance de paquet.

Supposons maintenant que la couche de notification réactive la notification trois fois en cas d'échec de remise de la notification.

Supposons maintenant que la couche de rapport d'erreurs réactive la couche de notification trois fois en cas d'échec de notification.

Supposons maintenant que le serveur Web réactive le rapport d'erreur trois fois en cas d'échec de l'erreur.

Supposons maintenant que le client Web renvoie la demande trois fois lorsqu'il obtient une erreur du serveur.

Supposons maintenant que la ligne sur le commutateur réseau qui est censée acheminer la notification à l'administrateur soit débranchée. Quand l'utilisateur du client Web reçoit-il enfin son message d'erreur? Je le fais environ douze minutes plus tard.

De peur que vous ne pensiez que ce n'est qu'un exemple stupide: nous avons vu ce bogue dans le code client, bien que bien pire que ce que j'ai décrit ici. Dans le code client particulier, l'écart entre la condition d'erreur qui se produit et celle qui est finalement signalée à l'utilisateur est de plusieurs semaines car de nombreuses couches sont automatiquement réessayées avec des attentes. Imaginez ce qui se passerait s'il y avait dix tentatives au lieu de trois .

Habituellement, la bonne chose à faire avec une condition d'erreur est de la signaler immédiatement et de laisser l'utilisateur décider quoi faire. Si l'utilisateur souhaite créer une stratégie de nouvelles tentatives automatiques, laissez-le créer cette stratégie au niveau approprié dans l'abstraction logicielle.

Eric Lippert
la source
19
+1. Raymond partage un exemple concret
SolutionYogi
210
-1 Ce conseil est inutile pour les défaillances transitoires du réseau rencontrées par les systèmes de traitement automatisé par lots.
2010
15
Je ne sais pas si cela signifie "ne le faites pas" suivi de "faites-le". La plupart des personnes posant cette question sont probablement les personnes travaillant dans l'abstraction logicielle.
Jim L
44
Lorsque vous avez des travaux par lots de longue durée qui utilisent des ressources réseau, telles que des services Web, vous ne pouvez pas vous attendre à ce que le réseau soit fiable à 100%. Il y aura des délais d'expiration occasionnels, des déconnexions de socket, peut-être même des problèmes de routage parasites ou des pannes de serveur qui se produisent pendant que vous l'utilisez. Une option consiste à échouer, mais cela peut signifier redémarrer un long travail plus tard. Une autre option consiste à réessayer plusieurs fois avec un délai approprié pour voir s'il s'agit d'un problème temporaire, puis échouer. Je suis d'accord sur la composition, dont vous devez être conscient .. mais c'est parfois le meilleur choix.
Erik Funkenbusch
18
Je pense que la citation que vous avez utilisée au début de votre réponse est intéressante. «Attendre des résultats différents» n'est une folie que si l'expérience antérieure vous donne régulièrement les mêmes résultats. Bien que le logiciel repose sur une promesse de cohérence, il existe certainement des circonstances dans lesquelles nous sommes tenus d'interagir avec des forces non fiables hors de notre contrôle.
Michael Richardson
49
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

Ensuite, vous appelleriez:

TryThreeTimes(DoSomething);

...Ou bien...

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

Une option plus flexible:

public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            Thread.Sleep(sleepPeriod);
        }
   }
}

A utiliser comme:

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

Une version plus moderne avec support pour async / wait:

public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            await action();
            return; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            await Task.Delay(sleepPeriod);
        }
   }
}

A utiliser comme:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
Drew Noakes
la source
2
changez de préférence le if en: --retryCount <= 0car cela continuera indéfiniment si vous souhaitez désactiver les tentatives en le définissant sur 0. Techniquement, le terme retryCountn'est pas un très bon nom, car il ne réessayera pas si vous le définissez sur 1. soit renommer à tryCountou mettre le - derrière.
Stefanvds
2
@saille je suis d'accord. Cependant, l'OP (et toutes les autres réponses) utilisent Thread.Sleep. Les alternatives sont d'utiliser des minuteries, ou plus probablement de nos jours asyncpour réessayer, avec Task.Delay.
Drew Noakes du
2
J'ai ajouté une version asynchrone.
Drew Noakes du
Ne casser que si l'action returns true? Func<bool>
Kiquenet
32

Le bloc d'application de gestion des erreurs transitoires fournit une collection extensible de stratégies de nouvelle tentative, notamment:

  • Incrémentale
  • Intervalle fixe
  • Arrêt exponentiel

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

Pour plus d'informations, voir ce chapitre du Guide du développeur.

Disponible via NuGet (recherchez « topaze »).

Grigori Melnik
la source
1
Intéressant. Pouvez-vous l'utiliser en dehors de Windows Azure, par exemple dans une application Winforms?
Matthew Lock
6
Absolument. Utilisez le mécanisme de nouvelle tentative et fournissez vos propres stratégies de détection. Nous les avons découplés intentionnellement. Trouvez le package de base de nuget ici: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik
2
De plus, le projet est désormais sous Apache 2.0 et accepte les contributions de la communauté. aka.ms/entlibopen
Grigori Melnik
1
@Alex. Ses morceaux en font la plate-forme.
Grigori Melnik
2
Ceci est maintenant obsolète, et la dernière fois que je l'ai utilisé, il contenait des bogues qui, à ma connaissance, ne le seront jamais et ne seront jamais corrigés: github.com/MicrosoftArchive/… .
Ohad Schneider
15

Autoriser les fonctions et réessayer les messages

public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
 Guard.IsNotNull(method, "method");            
 T retval = default(T);
 do
 {
   try
   {
     retval = method();
     return retval;
   }
   catch
   {
     onFailureAction();
      if (numRetries <= 0) throw; // improved to avoid silent failure
      Thread.Sleep(retryTimeout);
   }
} while (numRetries-- > 0);
  return retval;
}
Brian
la source
RetryMethod à retval sont True, oumax retries?
Kiquenet
14

Vous pouvez également envisager d'ajouter le type d'exception pour lequel vous souhaitez réessayer. Par exemple, s'agit-il d'une exception de délai d'attente que vous souhaitez réessayer? Une exception de base de données?

RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);

public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
    if (action == null)
        throw new ArgumentNullException("action");
    if (retryOnExceptionType == null)
        throw new ArgumentNullException("retryOnExceptionType");
    while (true)
    {
        try
        {
            action();
            return;
        }
        catch(Exception e)
        {
            if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
                throw;

            if (retryTimeout > 0)
                System.Threading.Thread.Sleep(retryTimeout);
        }
    }
}

Vous pouvez également noter que tous les autres exemples ont un problème similaire avec le test de nouvelles tentatives == 0 et réessayez à l'infini ou ne déclenchez pas d'exceptions lorsque vous donnez une valeur négative. Sleep (-1000) échouera également dans les blocs catch ci-dessus. Cela dépend de la façon dont vous vous attendez à ce que les gens soient stupides, mais la programmation défensive ne fait jamais de mal.

csharptest.net
la source
9
+1, mais pourquoi ne pas faire RetryForException <T> (...) où T: Exception, puis attraper (T e)? Je viens de l'essayer et cela fonctionne parfaitement.
TrueWill
Soit ou ici, car je n'ai rien à faire avec le type à condition que je pense qu'un ancien paramètre simple ferait l'affaire.
csharptest.net
@TrueWill attraperait apparemment (T ex) a quelques bugs selon ce post stackoverflow.com/questions/1577760/…
csharptest.net
3
Mise à jour: En fait, une meilleure implémentation que j'utilise utilise un délégué Predicate <Exception> qui retourne true si une nouvelle tentative est appropriée. Cela vous permet d'utiliser des codes d'erreur natifs ou d'autres propriétés de l'exception pour déterminer si une nouvelle tentative est applicable. Par exemple les codes HTTP 503.
csharptest.net
1
"Aussi Sleep (-1000) échouera dans les blocs catch ci-dessus" ... utilisez un TimeSpan et vous n'aurez pas ce problème. De plus, TimeSpan est beaucoup plus flexible et auto-descriptif. À partir de votre signature "int retryTimeout", comment savoir si retryTimeout est MS, secondes, minutes, années ?? ;-)
bytedev
13

Je suis fan des méthodes de récursivité et d'extension, voici donc mes deux cents:

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}
Martin RL
la source
7

Sur la base des travaux précédents, j'ai pensé à améliorer la logique des nouvelles tentatives de trois manières:

  1. Spécification du type d'exception à intercepter / réessayer. Il s'agit de l'amélioration principale, car une nouvelle tentative pour une exception est tout simplement erronée.
  2. Ne pas imbriquer le dernier essai dans un essai / capture, obtenant des performances légèrement meilleures
  3. En faire une Actionméthode d'extension

    static class ActionExtensions
    {
      public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
      {
        if (action == null)
          throw new ArgumentNullException("action");
    
        while( retries-- > 0 )
        {
          try
          {
            action( );
            return;
          }
          catch (T)
          {
            Thread.Sleep( retryDelay );
          }
        }
    
        action( );
      }
    }

La méthode peut alors être invoquée comme telle (des méthodes anonymes peuvent également être utilisées, bien sûr):

new Action( AMethodThatMightThrowIntermittentException )
  .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
Igor Pashchuk
la source
1
C'est excellent. Mais personnellement, je ne l'appellerais pas «retryTimeout» car ce n'est pas vraiment un Timeout. «RetryDelay», peut-être?
Holf
7

Restez simple avec C # 6.0

public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
    try
    {
        return action();
    }
    catch when (retryCount != 0)
    {
        await Task.Delay(retryInterval);
        return await Retry(action, retryInterval, --retryCount);
    }
}
Anders Skovborg
la source
2
Je suis un peu curieux, cela engendrerait-il une quantité folle de threads avec un nombre et un intervalle de tentatives élevés en raison du retour de la même méthode attendue?
HuntK24
6

Utilisez Polly

https://github.com/App-vNext/Polly-Samples

Voici un nouveau générique que j'utilise avec Polly

public T Retry<T>(Func<T> action, int retryCount = 0)
{
    PolicyResult<T> policyResult = Policy
     .Handle<Exception>()
     .Retry(retryCount)
     .ExecuteAndCapture<T>(action);

    if (policyResult.Outcome == OutcomeType.Failure)
    {
        throw policyResult.FinalException;
    }

    return policyResult.Result;
}

Utilisez-le comme ça

var result = Retry(() => MyFunction()), 3);
Erik Bergstedt
la source
5

Implémenté la réponse de LBushkin de la dernière façon:

    public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }

                await task();
                return;
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }

    public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }
                return await task();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }  

et pour l'utiliser:

await Retry.Do([TaskFunction], retryInterval, retryAttempts);

alors que la fonction [TaskFunction]peut être soit Task<T>ou juste Task.

Fabian Bigler
la source
1
Merci Fabian! Cela devrait être voté jusqu'au sommet!
JamesHoux
1
@MarkLauter la réponse courte est oui. ;-)
Fabian Bigler
4

Je mettrais en œuvre ceci:

public static bool Retry(int maxRetries, Func<bool, bool> method)
{
    while (maxRetries > 0)
    {
        if (method(maxRetries == 1))
        {
            return true;
        }
        maxRetries--;
    }
    return false;        
}

Je n'utiliserais pas les exceptions comme elles sont utilisées dans les autres exemples. Il me semble que si nous nous attendons à la possibilité qu'une méthode ne réussisse pas, son échec ne fait pas exception. Ainsi, la méthode que j'appelle doit retourner true si elle réussit et false si elle échoue.

Pourquoi est-ce un Func<bool, bool>et pas seulement un Func<bool>? Donc, si je veux qu'une méthode puisse lever une exception en cas d'échec, j'ai un moyen de l'informer que c'est le dernier essai.

Je pourrais donc l'utiliser avec du code comme:

Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       if (!succeeded && lastIteration)
       {
          throw new InvalidOperationException(...)
       }
       return succeeded;
   });

ou

if (!Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       return succeeded;
   }))
{
   Console.WriteLine("Well, that didn't work.");
}

Si passer un paramètre que la méthode n'utilise pas s'avère maladroit, il est trivial d'implémenter une surcharge Retryqui en prend juste un Func<bool>aussi.

Robert Rossney
la source
1
+1 pour éviter l'exception. Bien que je fasse une nouvelle tentative (...) et que je lance quelque chose? Les retours booléens et / ou les codes retour sont trop souvent ignorés.
csharptest.net
1
"si nous nous attendons à la possibilité qu'une méthode ne réussisse pas, son échec n'est pas une exception" - bien que cela soit vrai dans certains cas, une exception ne signifie pas nécessairement exceptionnelle. C'est pour la gestion des erreurs. Il n'y a aucune garantie que l'appelant vérifiera un résultat booléen. Il y a une garantie qu'une exception sera gérée (par le runtime fermant l'application si rien d'autre ne le fait).
TrueWill
Je ne trouve pas la référence mais je crois que .NET définit une exception comme "une méthode n'a pas fait ce qu'elle a dit qu'elle ferait". 1 but est d'utiliser des exceptions pour indiquer un problème plutôt que le modèle Win32 d'exiger que l'appelant vérifie la valeur de retour si la fonction a réussi ou non.
noctonura
Mais les exceptions ne signifient pas simplement "un problème". Ils incluent également une masse d'informations de diagnostic dont la compilation nécessite du temps et de la mémoire. Il y a clairement des situations où cela n'a pas la moindre importance. Mais il en existe beaucoup. .NET n'utilise pas d'exceptions pour le flux de contrôle (comparez, disons, avec l'utilisation de l' StopIterationexception par Python ), et il y a une raison.
Robert Rossney
Le TryDomodèle de méthode est une pente glissante. Avant de vous en rendre compte, toute votre pile d'appels sera constituée de TryDométhodes. Des exceptions ont été inventées pour éviter un tel gâchis.
HappyNomad
2

L'interruption exponentielle est une bonne stratégie de nouvelle tentative que de simplement essayer x nombre de fois. Vous pouvez utiliser une bibliothèque comme Polly pour l'implémenter.

utsavized
la source
2

Pour ceux qui souhaitent avoir à la fois la possibilité de réessayer sur n'importe quelle exception ou de définir explicitement le type d'exception, utilisez ceci:

public class RetryManager 
{
    public void Do(Action action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        Try<object, Exception>(() => {
            action();
            return null;
        }, interval, retries);
    }

    public T Do<T>(Func<T> action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        return Try<T, Exception>(
              action
            , interval
            , retries);
    }

    public T Do<E, T>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        return Try<T, E>(
              action
            , interval
            , retries);
    }

    public void Do<E>(Action action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        Try<object, E>(() => {
            action();
            return null;
        }, interval, retries);
    }

    private T Try<T, E>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        var exceptions = new List<E>();

        for (int retry = 0; retry < retries; retry++)
        {
            try
            {
                if (retry > 0)
                    Thread.Sleep(interval);
                return action();
            }
            catch (E ex)
            {
                exceptions.Add(ex);
            }
        }

        throw new AggregateException(exceptions);
    }
}
mrogunlana
la source
2

J'avais besoin d'une méthode qui prend en charge l'annulation, alors que j'y étais, j'ai ajouté la prise en charge du retour des échecs intermédiaires.

public static class ThreadUtils
{
    public static RetryResult Retry(
        Action target,
        CancellationToken cancellationToken,
        int timeout = 5000,
        int retries = 0)
    {
        CheckRetryParameters(timeout, retries)
        var failures = new List<Exception>();
        while(!cancellationToken.IsCancellationRequested)
        {
            try
            {
                target();
                return new RetryResult(failures);
            }
            catch (Exception ex)
            {
                failures.Add(ex);
            }

            if (retries > 0)
            {
                retries--;
                if (retries == 0)
                {
                    throw new AggregateException(
                     "Retry limit reached, see InnerExceptions for details.",
                     failures);
                }
            }

            if (cancellationToken.WaitHandle.WaitOne(timeout))
            {
                break;
            }
        }

        failures.Add(new OperationCancelledException(
            "The Retry Operation was cancelled."));
        throw new AggregateException("Retry was cancelled.", failures);
    }

    private static void CheckRetryParameters(int timeout, int retries)
    {
        if (timeout < 1)
        {
            throw new ArgumentOutOfRangeException(...
        }

        if (retries < 0)
        {
            throw new ArgumentOutOfRangeException(...

        }
    }

    public class RetryResult : IEnumerable<Exception>
    {
        private readonly IEnumerable<Exception> failureExceptions;
        private readonly int failureCount;

         protected internal RetryResult(
             ICollection<Exception> failureExceptions)
         {
             this.failureExceptions = failureExceptions;
             this.failureCount = failureExceptions.Count;
         }
    }

    public int FailureCount
    {
        get { return this.failureCount; }
    }

    public IEnumerator<Exception> GetEnumerator()
    {
        return this.failureExceptions.GetEnumerator();
    }

    System.Collections.IEnumerator 
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Vous pouvez utiliser la Retryfonction comme ceci, réessayez 3 fois avec un délai de 10 secondes mais sans annulation.

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        CancellationToken.None,
        10000,
        3);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // oops, 3 retries wasn't enough.
}

Ou, réessayez éternellement toutes les cinq secondes, sauf annulation.

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        someTokenSource.Token);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // operation was cancelled before success.
}

Comme vous pouvez le deviner, dans mon code source, j'ai surchargé la Retryfonction pour prendre en charge les différents types de delgate que je souhaite utiliser.

Jodrell
la source
2

Cette méthode permet de réessayer sur certains types d'exceptions (lance immédiatement d'autres).

public static void DoRetry(
    List<Type> retryOnExceptionTypes,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
{
    for (var i = 0; i < retryCount; ++i)
    {
        try
        {
            actionToTry();
            break;
        }
        catch (Exception ex)
        {
            // Retries exceeded
            // Throws on last iteration of loop
            if (i == retryCount - 1) throw;

            // Is type retryable?
            var exceptionType = ex.GetType();
            if (!retryOnExceptionTypes.Contains(exceptionType))
            {
                throw;
            }

            // Wait before retry
            Thread.Sleep(msWaitBeforeEachRety);
        }
    }
}
public static void DoRetry(
    Type retryOnExceptionType,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
        => DoRetry(new List<Type> {retryOnExceptionType}, actionToTry, retryCount, msWaitBeforeEachRety);

Exemple d'utilisation:

DoRetry(typeof(IOException), () => {
    using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(entryBytes, 0, entryBytes.Length);
    }
});
Tom Gullen
la source
1

Mise à jour après 6 ans: maintenant je considère que l'approche ci-dessous est assez mauvaise. Pour créer une logique de nouvelle tentative, nous devons envisager d'utiliser une bibliothèque comme Polly.


Ma asyncmise en œuvre de la méthode de nouvelle tentative:

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

        for (int retry = 0; retry < retryCount; retry++)
        {
            try
            {
                return await action().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }

            await Task.Delay(retryInterval).ConfigureAwait(false);
        }
        throw new AggregateException(exceptions);
    }

Points clés: j'ai utilisé .ConfigureAwait(false);et à la Func<dynamic>placeFunc<T>

Cihan Uygun
la source
Cela ne fournit pas de réponse à la question. Veuillez envisager de publier votre réponse en tant que nouvelle question, en utilisant le bouton "Poser une question" en haut de la page, puis en affichant votre propre réponse à la question pour partager ce que vous avez appris avec la communauté.
elixenide
Beaucoup plus simple avec C # 5.0 que codereview.stackexchange.com/q/55983/54000 mais peut-être que CansellactionToken devrait être injecté.
SerG
Il y a un problème avec cette implémentation. Après la dernière tentative, juste avant d'abandonner, Task.Delayest appelé sans raison.
HappyNomad
@HappyNomad c'est une réponse vieille de 6 ans et maintenant je considère que c'est une assez mauvaise approche pour créer une logique de nouvelle tentative :)) merci pour la notification. Je mettrai à jour ma réponse en fonction de cette considération.
Cihan Uygun
0

Ou que diriez-vous de le faire un peu plus net ....

int retries = 3;
while (retries > 0)
{
  if (DoSomething())
  {
    retries = 0;
  }
  else
  {
    retries--;
  }
}

Je pense que les exceptions devraient généralement être évitées en tant que mécanisme, à moins que vous ne les dépassiez (comme la construction d'une bibliothèque que d'autres personnes peuvent utiliser). Pourquoi ne pas simplement DoSomething()renvoyer la commande truesi elle a réussi et falsesinon?

EDIT: Et cela peut être encapsulé dans une fonction comme d'autres l'ont suggéré. Le seul problème est que si vous n'écrivez pas la DoSomething()fonction vous-même

Mike
la source
7
"Je crois que le fait de lever des exceptions devrait généralement être évité en tant que mécanisme à moins que vous ne les dépassiez entre les frontières" - je ne suis absolument pas d'accord. Comment savez-vous que l'appelant a vérifié votre retour faux (ou pire, nul)? POURQUOI le code a-t-il échoué? Faux ne vous dit rien d'autre. Que faire si l'appelant doit transmettre l'échec à la pile? Lisez msdn.microsoft.com/en-us/library/ms229014.aspx - ce sont pour les bibliothèques, mais elles ont tout autant de sens pour le code interne. Et dans une équipe, d'autres personnes sont susceptibles d'appeler votre code.
TrueWill
0

J'ai eu besoin de passer un paramètre à ma méthode pour réessayer et avoir une valeur de résultat; j'ai donc besoin d'une expression .. Je construis cette classe qui fait le travail (elle est inspirée de celle de LBushkin) vous pouvez l'utiliser comme ceci:

static void Main(string[] args)
{
    // one shot
    var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);

    // delayed execute
    var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
    var res2 = retry.Execute();
}

static void fix()
{
    Console.WriteLine("oh, no! Fix and retry!!!");
}

static string retryThis(string tryThis)
{
    Console.WriteLine("Let's try!!!");
    throw new Exception(tryThis);
}

public class Retry<TResult>
{
    Expression<Func<TResult>> _Method;
    int _NumRetries;
    TimeSpan _RetryTimeout;
    Action _OnFailureAction;

    public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
    {
        _Method = method;
        _NumRetries = numRetries;
        _OnFailureAction = onFailureAction;
        _RetryTimeout = retryTimeout;
    }

    public TResult Execute()
    {
        TResult result = default(TResult);
        while (_NumRetries > 0)
        {
            try
            {
                result = _Method.Compile()();
                break;
            }
            catch
            {
                _OnFailureAction();
                _NumRetries--;
                if (_NumRetries <= 0) throw; // improved to avoid silent failure
                Thread.Sleep(_RetryTimeout);
            }
        }
        return result;
    }

    public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
    {
        var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
        return retry.Execute();
    }
}

ps. la solution de LBushkin fait une nouvelle tentative = D

Paolo Sanchi
la source
0

J'ajouterais le code suivant à la réponse acceptée

public static class Retry<TException> where TException : Exception //ability to pass the exception type
    {
        //same code as the accepted answer ....

        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 (TException ex) //Usage of the exception type
                {
                    exceptions.Add(ex);
                    Thread.Sleep(retryInterval);
                }
            }

            throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
        }
    }

Fondamentalement, le code ci-dessus rend la Retryclasse générique afin que vous puissiez passer le type de l'exception que vous souhaitez intercepter pour réessayer.

Maintenant, utilisez-le presque de la même manière, mais en spécifiant le type d'exception

Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
Juan M. Elosegui
la source
La boucle for s'exécutera toujours plusieurs fois (en fonction de votre retryCount) même si le code de la boucle TRY CATCH a été exécuté sans exception. Je suggérerais de définir retryCount égal au var de nouvelle tentative dans la boucle d'essai, de sorte que la boucle for ne s'arrêtera pas dessus.
scre_www
@scre_www Je crois que vous vous trompez. Si actionne lance pas, Doretourne donc breakloin de la forboucle.
HappyNomad
Dans tous les cas, il y a un problème avec cette implémentation. Après la dernière tentative, juste avant d'abandonner, Thread.Sleepest appelé sans raison.
HappyNomad
0

Je sais que cette réponse est très ancienne, mais je voulais juste faire un commentaire à ce sujet car j'ai rencontré des problèmes en les utilisant pendant que, faites, quelle que soit la déclaration avec les compteurs.

Au fil des ans, je me suis fixé une meilleure approche, je pense. C'est d'utiliser une sorte d'agrégation d'événements comme une extension réactive "Sujet" ou similaire. Lorsqu'un essai échoue, vous publiez simplement un événement indiquant l'échec de l'essai et demandez à la fonction d'agrégateur de replanifier l'événement. Cela vous permet de contrôler beaucoup plus la relance sans polluer l'appel lui-même avec un tas de boucles de relance et ce qui ne l'est pas. Vous n'attachez pas non plus un seul fil avec un tas de fils en sommeil.

Brandon
la source
0

Faites-le simplement en C #, Java ou d'autres langages:

  internal class ShouldRetryHandler {
    private static int RETRIES_MAX_NUMBER = 3;
    private static int numberTryes;

    public static bool shouldRetry() {
        var statusRetry = false;

        if (numberTryes< RETRIES_MAX_NUMBER) {
            numberTryes++;
            statusRetry = true;
            //log msg -> 'retry number' + numberTryes

        }

        else {
            statusRetry = false;
            //log msg -> 'reached retry number limit' 
        }

        return statusRetry;
    }
}

et utilisez-le très simplement dans votre code:

 void simpleMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    //do some repetitive work
     }

    //some code    
    }

ou vous pouvez l'utiliser dans des méthodes récursives:

void recursiveMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }

    //some code    
    }
Choletski
la source
0
int retries = 3;
while (true)
{
    try
    {
        //Do Somthing
        break;
    }
    catch (Exception ex)
    {
        if (--retries == 0)
            return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
        else
            System.Threading.Thread.Sleep(100);
    }
Bhaskar
la source
Que faites-vous Request.BadRequest?
Danh
0

Assistant de nouvelle tentative: une implémentation java générique qui contient à la fois des tentatives de retour et de type void.

import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryHelper {
  private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
  private int retryWaitInMS;
  private int maxRetries;

  public RetryHelper() {
    this.retryWaitInMS = 300;
    this.maxRetries = 3;
  }

  public RetryHelper(int maxRetry) {
    this.maxRetries = maxRetry;
    this.retryWaitInMS = 300;
  }

  public RetryHelper(int retryWaitInSeconds, int maxRetry) {
    this.retryWaitInMS = retryWaitInSeconds;
    this.maxRetries = maxRetry;
  }

  public <T> T retryAndReturn(Supplier<T> supplier) {
    try {
      return supplier.get();
    } catch (Exception var3) {
      return this.retrySupplier(supplier);
    }
  }

  public void retry(Runnable runnable) {
    try {
      runnable.run();
    } catch (Exception var3) {
      this.retrySupplier(() -> {
        runnable.run();
        return null;
      });
    }

  }

  private <T> T retrySupplier(Supplier<T> supplier) {
    log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
    int retryCounter = 0;

    while(retryCounter < this.maxRetries) {
      try {
        return supplier.get();
      } catch (Exception var6) {
        ++retryCounter;
        log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
        if (retryCounter >= this.maxRetries) {
          log.error("Max retries exceeded.");
          throw var6;
        }

        try {
          Thread.sleep((long)this.retryWaitInMS);
        } catch (InterruptedException var5) {
          var5.printStackTrace();
        }
      }
    }

    return supplier.get();
  }

  public int getRetryWaitInMS() {
    return this.retryWaitInMS;
  }

  public int getMaxRetries() {
    return this.maxRetries;
  }
}

Usage:

    try {
      returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
      //or no return type:
      new RetryHelper().retry(() -> mytask(args));
    } catch(Exception ex){
      log.error(e.getMessage());
      throw new CustomException();
    }
divya jain
la source
0

Voici une version async/ awaitqui regroupe les exceptions et prend en charge l'annulation.

/// <seealso href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry"/>
protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
{
    var exceptions = new List<Exception>();

    for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
        try {
            return await action().ConfigureAwait( false );
        } catch ( Exception ex ) {
            exceptions.Add( ex );

            if ( retries < maxRetries )
                await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
            else
                throw new AggregateException( "Retry limit reached", exceptions );
        }

    exceptions.Add( new OperationCanceledException( cancelToken ) );
    throw new AggregateException( "Retry loop was canceled", exceptions );
}
HappyNomad
la source
-1
public delegate void ThingToTryDeletage();

public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime)
{
   while(true)
   {
      try
      {
        ThingToTryDelegate();
      } catch {

            if( --N == 0) throw;
          else Thread.Sleep(time);          
      }
}
Mark P Neyer
la source
Parce que throw;c'est la seule façon de terminer la boucle infinie, cette méthode implémente en fait "essayer jusqu'à ce qu'elle échoue N fois" et non pas "essayer jusqu'à Nfois jusqu'à ce qu'elle réussisse". Vous avez besoin d'un break;ou return;après l'appel ThingToTryDelegate();sinon il sera appelé en continu s'il n'échoue jamais. De plus, cela ne se compilera pas car le premier paramètre de TryNTimesn'a pas de nom. -1.
BACON
-1

J'ai écrit une petite classe basée sur les réponses publiées ici. J'espère que cela aidera quelqu'un: https://github.com/natenho/resiliency

using System;
using System.Threading;

/// <summary>
/// Classe utilitária para suporte a resiliência
/// </summary>
public sealed class Resiliency
{
    /// <summary>
    /// Define o valor padrão de número de tentativas
    /// </summary>
    public static int DefaultRetryCount { get; set; }

    /// <summary>
    /// Define o valor padrão (em segundos) de tempo de espera entre tentativas
    /// </summary>
    public static int DefaultRetryTimeout { get; set; }

    /// <summary>
    /// Inicia a parte estática da resiliência, com os valores padrões
    /// </summary>
    static Resiliency()
    {
        DefaultRetryCount = 3;
        DefaultRetryTimeout = 0;
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes  quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
    public static void Try(Action action)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
    {
        Try<Exception>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, retryCount, retryTimeout, tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action) where TException : Exception
    {
        Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    public static void Try<TException>(Action action, int retryCount) where TException : Exception
    {
        Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    /// <param name="retryTimeout"></param>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
    {
        Try<TException>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        while (retryCount-- > 0)
        {
            try
            {
                action();
                return;
            }
            catch (TException ex)
            {
                //Executa o manipulador de exception
                if (tryHandler != null)
                {
                    var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
                    tryHandler(callback);
                    //A propriedade que aborta pode ser alterada pelo cliente
                    if (callback.AbortRetry)
                        throw;
                }

                //Aguarda o tempo especificado antes de tentar novamente
                Thread.Sleep(retryTimeout);
            }
        }

        //Na última tentativa, qualquer exception será lançada de volta ao chamador
        action();
    }

}

/// <summary>
/// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
/// </summary>
public class ResiliencyTryHandler<TException> where TException : Exception
{
    #region Properties

    /// <summary>
    /// Opção para abortar o ciclo de tentativas
    /// </summary>
    public bool AbortRetry { get; set; }

    /// <summary>
    /// <see cref="Exception"/> a ser tratada
    /// </summary>
    public TException Exception { get; private set; }

    /// <summary>
    /// Identifca o número da tentativa atual
    /// </summary>
    public int CurrentTry { get; private set; }

    #endregion

    #region Constructors

    /// <summary>
    /// Instancia um manipulador de tentativa. É utilizado internamente
    /// por <see cref="Resiliency"/> para permitir que o cliente altere o
    /// comportamento do ciclo de tentativas
    /// </summary>
    public ResiliencyTryHandler(TException exception, int currentTry)
    {
        Exception = exception;
        CurrentTry = currentTry;
    }

    #endregion

}
natenho
la source
-1

J'ai implémenté une version asynchrone de la réponse acceptée comme ça - et cela semble bien fonctionner - des commentaires?


        public static async Task DoAsync(
            Action action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            DoAsync<object>(() =>
            {
                action();
                return null;
            }, retryInterval, maxAttemptCount);
        }

        public static async Task<T> DoAsync<T>(
            Func<Task<T>> action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            var exceptions = new List<Exception>();

            for (int attempted = 0; attempted < maxAttemptCount; attempted++)
            {
                try
                {
                    if (attempted > 0)
                    {
                        Thread.Sleep(retryInterval);
                    }
                    return await action();
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            throw new AggregateException(exceptions);
        }

Et, appelez-le simplement comme ceci:

var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);
Kerneels Roos
la source
Thread.Sleep? Le blocage d'un thread annule les avantages de l'asynchronie. Je suis également sûr que la Task DoAsync()version devrait accepter un argument de type Func<Task>.
Theodor Zoulias