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()
?
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
.
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.
Policy
.Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
.Execute(() => DoSomething());
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.
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);
Le Bloc d'applications de gestion des pannes transitoires fournit une collection extensible de stratégies de nouvelle tentative, notamment:
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 ').
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;
}
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.
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);
}
}
En me basant sur le travail précédent, j'ai envisagé d'améliorer la logique de nouvelle tentative de trois manières:
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 ) );
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);
}
}
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);
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
.
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>
.
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);
}
}
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.
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.
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>
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()
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);
}
});
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);
}
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
}
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));
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;
}
}
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();
}
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.
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