c# .net

c# - ¿La forma más limpia de escribir la lógica de reintento?



.net (25)

De vez en cuando tengo la necesidad de volver a intentar una operación varias veces antes de rendirme. Mi código es como

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

Me gustaría reescribir esto en una función de reintento general como:

TryThreeTimes(DoSomething);

¿Es posible en C #? ¿Cuál sería el código para el método TryThreeTimes() ?


Deberías probar con Polly . Es una biblioteca .NET escrita por mí que permite a los desarrolladores expresar políticas de manejo de excepciones transitorias como Reintentar, Reintentar para siempre, Esperar y Reintentar o Interruptor de circuitos de manera fluida.

Ejemplo

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


El bloque de aplicación de manejo de fallas transitorias proporciona una colección extensible de estrategias de reintento que incluyen:

  • Incremental
  • Intervalo fijo
  • Retroceso exponencial

También incluye una colección de estrategias de detección de errores para servicios basados ​​en la nube.

Para obtener más información, consulte este capítulo de la Guía del desarrollador.

Disponible a través de NuGet (búsqueda de '' topacio '').


Esta es posiblemente una mala idea. Primero, es emblemático de la máxima "la definición de locura es hacer lo mismo dos veces y esperar resultados diferentes cada vez". Segundo, este patrón de codificación no se compone bien consigo mismo. Por ejemplo:

Supongamos que la capa de hardware de su red reenvía un paquete tres veces si falla, esperando, digamos, un segundo entre fallas.

Ahora suponga que la capa de software reenvía una notificación sobre una falla tres veces en la falla de un paquete.

Ahora suponga que la capa de notificación reactiva la notificación tres veces en un error de entrega de notificación.

Ahora suponga que la capa de informe de errores reactiva la capa de notificación tres veces en un error de notificación.

Y ahora supongamos que el servidor web reactiva el informe de errores tres veces en caso de error.

Y ahora suponga que el cliente web reenvía la solicitud tres veces al recibir un error del servidor.

Ahora suponga que la línea en el conmutador de red que debe enrutar la notificación al administrador está desconectada. ¿Cuándo el usuario del cliente web finalmente recibe su mensaje de error? Lo hago unos doce minutos después.

Para que no piense que esto es solo un ejemplo tonto: hemos visto este error en el código del cliente, aunque mucho, mucho peor de lo que he descrito aquí. En el código de cliente en particular, la brecha entre la condición de error y el hecho de que se informara al usuario fue de varias semanas porque muchas capas volvían a intentar automáticamente con las esperas. Solo imagina lo que pasaría si hubiera diez reintentos en lugar de tres .

Por lo general, lo correcto para hacer una condición de error es informarla inmediatamente y dejar que el usuario decida qué hacer. Si el usuario desea crear una política de reintentos automáticos, permítales crear esa política en el nivel apropiado en la abstracción del software.


Hazlo simple en C #, Java u otros lenguajes:

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

y utilízalo en tu código muy simple:

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

o puedes usarlo en métodos recursivos:

void recursiveMethod(){ //some code if(ShouldRetryHandler.shouldRetry()){ recursiveMethod(); } //some code }


Implementó la respuesta de LBushkin de la última manera:

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

y para usarlo:

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

mientras que la función [TaskFunction] puede ser Task<T> o simplemente Task .


Las declaraciones de captura general que simplemente reintentan la misma llamada pueden ser peligrosas si se usan como un mecanismo general de manejo de excepciones. Habiendo dicho eso, aquí hay una envoltura de reintentos basada en lambda que puede usar con cualquier método. Elegí factorizar el número de reintentos y el tiempo de espera de reintento como parámetros para un poco más de flexibilidad:

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

Ahora puede utilizar este método de utilidad para realizar la lógica de reintento:

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

o:

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

o:

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

O incluso podrías hacer una sobrecarga async .


Mantenlo simple con 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); } }


Mi implementación async del método de reintento:

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

Puntos clave: utilicé .ConfigureAwait(false); y Func<dynamic> lugar de Func<T>


Necesitaba un método que soporte la cancelación, mientras estaba en ello, agregué soporte para devolver fallas intermedias.

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

Puede usar la función Retry esta manera, reintentar 3 veces con un retraso de 10 segundos pero sin cancelación.

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. }

O, vuelva a intentarlo eternamente cada cinco segundos, a menos que se cancele.

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. }

Como puede adivinar, en mi código fuente he sobrecargado la función Retry para admitir los diferentes tipos de delgate que deseo usar.


O qué tal si lo hacemos un poco más ordenado ...

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

Creo que, por lo general, se deben evitar las excepciones de lanzamiento como un mecanismo a menos que se las pase de un lado a otro (como construir una biblioteca que otras personas puedan usar). ¿Por qué no hacer que el DoSomething() devuelva true si fue exitoso y false contrario?

EDIT: Y esto se puede encapsular dentro de una función como otros también han sugerido. El único problema es si no está escribiendo la función DoSomething() usted mismo


Para aquellos que desean tener la opción de reintentar en cualquier excepción o establecer explícitamente el tipo de excepción, use esto:

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


Permitir funciones y reintentar mensajes.

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


Sé que esta respuesta es muy antigua, pero solo quería hacer un comentario sobre esto porque me he encontrado con problemas al usar esto mientras hago lo que sea con los contadores.

A lo largo de los años me he decidido por un enfoque mejor, creo. Eso es usar algún tipo de agregación de eventos como las extensiones reactivas "Asunto" o similares. Cuando falla un intento, simplemente publica un evento que dice que el intento falló y la función agregadora vuelve a programar el evento. Esto le permite mucho más control sobre el reintento sin contaminar la llamada en sí misma con un montón de ciclos de reintentos y qué no. Tampoco estás atando un solo hilo con un montón de hilos durmiendo.


Sobre la base del trabajo anterior, pensé en mejorar la lógica de reintento de tres maneras:

  1. Especificar qué tipo de excepción se debe capturar / reintentar. Esta es la mejora principal, ya que reintentar para cualquier excepción es simplemente erróneo.
  2. No anidar el último intento en un try / catch, logrando un rendimiento ligeramente mejor
  3. Haciéndolo un método de extensión de 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( ); } }

El método puede entonces invocarse así (también se pueden usar métodos anónimos, por supuesto):

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


Soy un fanático de los métodos de recursión y extensión, así que aquí están mis dos centavos:

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


También puede considerar agregar el tipo de excepción que desea reintentar. Por ejemplo, ¿es esta una excepción de tiempo de espera que desea volver a intentar? ¿Una excepción de base de datos?

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

También puede tener en cuenta que todos los demás ejemplos tienen un problema similar con las pruebas de reintentos == 0 y reintentar el infinito o no pueden generar excepciones cuando se les da un valor negativo. También Sleep (-1000) fallará en los bloques de captura anteriores. Depende de qué tan "tonta" esperas que sea la gente, pero la programación defensiva nunca duele.


Tuve la necesidad de pasar algún parámetro a mi método para volver a intentarlo y tener un valor de resultado; así que necesito una expresión ... Construyo esta clase que hace el trabajo (está inspirada en la de LBushkin). Puedes usarla así:

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

PD. La solución de LBushkin hace un reintento más = D


Usar polly

github.com/App-vNext/Polly-Samples

Aquí hay un reintento genérico que uso con 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; }

Usalo asi

var result = Retry(() => MyFunction()), 3);


Yo agregaría el siguiente código a la respuesta aceptada

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

Básicamente, el código anterior hace que la clase Retry genérica para que pueda pasar el tipo de excepción que desea capturar para reintentar.

Ahora utilícelo casi de la misma manera pero especificando el tipo de excepción

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


Yo implementaría esto:

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

No usaría excepciones como se usan en los otros ejemplos. Me parece que si estamos esperando la posibilidad de que un método no tenga éxito, su falla no es una excepción. Así que el método al que estoy llamando debería devolver verdadero si tuvo éxito, y falso si falló.

¿Por qué es un Func<bool, bool> y no solo un Func<bool> ? Entonces, si quiero un método para poder lanzar una excepción en caso de falla, tengo una manera de informarle que este es el último intento.

Así que podría usarlo con código como:

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

o

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

Si pasar un parámetro que el método no usa resulta ser incómodo, es trivial implementar una sobrecarga de Retry que solo toma una función Func<bool> también.


El retroceso exponencial es una buena estrategia para reintentar que simplemente intentar x veces. Puedes usar una biblioteca como Polly para implementarla.


He escrito una pequeña clase basada en las respuestas publicadas aquí. Esperemos que ayude a alguien: 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://.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 }


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


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


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

Entonces llamarías:

TryThreeTimes(DoSomething);

...o alternativamente...

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

Una opción más 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); } } }

Para ser utilizado como:

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

Una versión más moderna con soporte para async / await:

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

Para ser utilizado como:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);