web-dev-qa-db-fra.com

Plus propre moyen d'écrire réessayer la logique?

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 nouvelle tentative générale comme:

TryThreeTimes(DoSomething);

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

421
noctonura

Les instructions de capture générales qui réessayent simplement le même appel peuvent être dangereuses si elles sont utilisées comme mécanisme général de gestion des exceptions. Ceci 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 l'expiration du délai d'attente 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 effectuer une nouvelle tentative de logique:

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 pouvez même créer une surcharge async.

530
LBushkin

Vous devriez essayer Polly . C'est une bibliothèque .NET écrite par moi qui permet aux développeurs d'exprimer des règles transitoires de gestion des exceptions telles que Réessayer, Réessayer pour toujours, Attendre et réessayer ou Circuit Breaker de manière fluide.

Exemple

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());
200

C'est peut-être une mauvaise idée. Premièrement, il est emblématique de la maxime "la définition de la folie fait la même chose deux fois et attend des résultats différents à chaque fois". Deuxièmement, ce modèle de codage ne 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 une seconde entre les échecs.

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

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

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

Et supposons maintenant que le serveur Web réactive le signalement des erreurs trois fois.

Et supposons maintenant que le client Web renvoie la demande trois fois après avoir obtenu une erreur du serveur.

Supposons maintenant que la ligne du commutateur réseau 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 vers douze minutes plus tard.

De peur que vous ne pensiez que ceci est juste un exemple stupide: nous avons vu ce bogue dans le code client, bien que ce soit bien pire que ce que j'ai décrit ici. Dans le code client particulier, l’écart entre la condition d’erreur et le signalement final à l’utilisateur était de plusieurs semaines car de nombreuses couches étaient automatiquement relancées avec des attentes. 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 signalez-la immédiatement et laissez l'utilisateur décider quoi faire. Si l'utilisateur veut créer une stratégie de tentatives automatiques, laissez-le créer cette stratégie au moment approprié. niveau dans l'abstraction logicielle.

60
Eric Lippert
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

Ensuite, vous appelez:

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 prenant en charge 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);
42
Drew Noakes

Le Bloc d'applications de gestion des pannes transitoires fournit une collection extensible de stratégies de nouvelle tentative, notamment:

  • Incrémentale
  • Intervalle fixe
  • Recul exponentiel

Il inclut également un ensemble de stratégies de détection d'erreur pour les services basés sur le cloud.

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

Disponible via NuGet (recherche de ' topaz ').

32
Grigori Melnik

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;
}
15
Brian

Vous pouvez également envisager d'ajouter le type d'exception que 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 présentent un problème similaire en ce qui concerne le test des tentatives == 0 et que vous essayez de nouveau à l'infini ou que vous ne parvenez pas à générer des exceptions lorsque vous leur attribuez une valeur négative. Sleep (-1000) échouera également dans les blocs catch ci-dessus. Cela dépend de la stupidité que vous attendez des gens, mais la programmation défensive ne fait jamais de mal.

14
csharptest.net

Je suis un partisan 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);
    }
}
11
Martin R-L

En me basant sur le travail précédent, j'ai envisagé d'améliorer la logique de nouvelle tentative de trois manières:

  1. Spécifier le type d'exception à intercepter/réessayer. Il s'agit de l'enrichissement principal, car une nouvelle tentative pour une exception est tout simplement fausse.
  2. Ne pas imbriquer le dernier essai dans un essai/attraper, obtenir des performances légèrement meilleures
  3. En faisant une méthode d'extension Action

    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 ensuite être invoquée de la manière suivante (des méthodes anonymes peuvent également être utilisées):

new Action( AMethodThatMightThrowIntermittentException )
  .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
7
Igor Pashchuk

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);
    }
}
6
Anders Skovborg

Utilisez Polly

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

Voici une nouvelle tentative 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);
5
Erik Bergstedt

Implémenté la réponse de LBouchkin dans la dernière mode:

    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 de l'utiliser:

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

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

5
Fabian Bigler

Je mettrais ceci en application:

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. Donc, 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 simplement un Func<bool>? Ainsi, si je veux une méthode pour pouvoir lever une exception en cas d’échec, j’ai le moyen de l’informer que c’est le dernier essai.

Je pourrais donc l'utiliser avec un 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 transmettre un paramètre que la méthode n'utilise pas s'avère gênant, il est trivial d'implémenter une surcharge de Retry qui prend simplement un Func<bool>.

4
Robert Rossney

Pour ceux qui souhaitent avoir la possibilité de réessayer 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);
    }
}
2
mrogunlana

Exponential backoff est une bonne stratégie de relance que d'essayer x nombre de fois. Vous pouvez utiliser une bibliothèque comme Polly pour l'implémenter.

1
utsavized

J'avais besoin d'une méthode qui prenne en charge l'annulation. J'y ai ajouté une prise en charge du renvoi 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 fonction Retry 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 fonction Retry pour prendre en charge les différents types de delgate que je souhaite utiliser.

1
Jodrell

Ma async implémentation 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 Func<dynamic> à la place Func<T>

1
Cihan Uygun

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

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

Je pense que les exceptions en jetant devraient généralement être évitées en tant que mécanisme sauf si vous les passez entre deux frontières (comme construire une bibliothèque que d'autres personnes peuvent utiliser). Pourquoi ne pas simplement faire en sorte que la commande DoSomething() renvoie true si l'opération a abouti et false sinon?

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

0
mike

Cette méthode autorise de nouvelles tentatives sur certains types d’exception (en jette d’autres immédiatement).

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);
    }
});
0
Tom Gullen
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);
    }
0
Bhaskar

Faites-le simple en C #, Java ou dans 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 dans votre code très simple:

 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    
    }
0
Choletski

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 classe Retry générique afin que vous puissiez transmettre le type de l'exception que vous souhaitez intercepter pour une nouvelle tentative.

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));
0
Juan M. Elosegui

Retry helper: implémentation Java générique contenant à la fois des tentatives renvoyables et des tentatives vides.

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

Je sais que cette réponse est très ancienne, mais je voulais juste faire un commentaire à ce sujet car des problèmes se posent lorsque je les utilise, que je fais, quelle que soit la déclaration avec des compteurs.

Au fil des ans, j’ai opté pour une meilleure approche, je pense. Cela consiste à utiliser une sorte d'agrégation d'événements comme une extension réactive "Sujet" ou similaire. Quand une tentative échoue, vous publiez simplement un événement en indiquant que la tentative a échoué et vous demandez à la fonction d'agrégation de planifier à nouveau l'événement. Cela vous permet de contrôler beaucoup plus vos tentatives sans polluer l’appel lui-même avec un tas de nouvelles tentatives. Vous n'attachez pas non plus un seul fil avec un tas de fils en sommeil.

0
Brandon

J'ai eu besoin de passer un paramètre à ma méthode pour réessayer et d'avoir une valeur de résultat; donc j'ai besoin d'une expression .. Je construis cette classe qui fait le travail (elle est inspirée de celle de LBouchkin), 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 encore une tentative = D

0
Paolo Sanchi