Maneira mais limpa de escrever a lógica de nova tentativa?

Ocasionalmente, eu preciso repetir uma operação várias vezes antes de desistir. Meu código é como:

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

Eu gostaria de rewrite isso em uma function de repetição geral como:

 TryThreeTimes(DoSomething); 

É possível em c #? Qual seria o código do método TryThreeTimes() ?

As instruções catch catch que simplesmente tentam novamente a mesma chamada podem ser perigosas se usadas como um mecanismo geral de tratamento de exceções. Dito isto, aqui está um wrapper baseado em lambda que você pode usar com qualquer método. Eu escolhi fatorar o número de novas tentativas e o tempo limite de nova tentativa como parâmetros para um pouco mais de flexibilidade:

 public static class Retry { public static void Do( Action action, TimeSpan retryInterval, int maxAttemptCount = 3) { Do(() => { action(); return null; }, retryInterval, maxAttemptCount); } public static T Do( Func action, TimeSpan retryInterval, int maxAttemptCount = 3) { var exceptions = new List(); 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); } } 

Agora você pode usar esse método de utilitário para executar a lógica de repetição:

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

ou:

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

ou:

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

Ou você pode até mesmo fazer uma sobrecarga async .

Você deveria tentar Polly . É uma biblioteca .NET escrita por mim que permite aos desenvolvedores expressarem políticas de tratamento de exceções transitórias, como Repetir, Repetir para Sempre, Aguardar e Repetir ou Disjuntor de maneira fluente.

Exemplo

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

Esta é possivelmente uma má ideia. Primeiro, é emblemático da máxima “a definição de insanidade é fazer a mesma coisa duas vezes e esperar resultados diferentes a cada vez”. Em segundo lugar, esse padrão de codificação não compõe bem a si mesmo. Por exemplo:

Suponha que sua camada de hardware de rede reenvia um pacote três vezes em caso de falha, esperando, digamos, um segundo entre falhas.

Agora suponha que a camada de software reenvie uma notificação sobre uma falha três vezes na falha do pacote.

Agora, suponha que a camada de notificação reative a notificação três vezes em uma falha de entrega de notificação.

Agora, suponha que a camada de relatório de erros reative a camada de notificação três vezes em uma falha de notificação.

E agora suponha que o servidor da web reative o relatório de erros três vezes por falha de erro.

E agora suponha que o cliente da Web reenvie a solicitação três vezes ao obter um erro do servidor.

Agora, suponha que a linha no comutador de rede que deveria rotear a notificação para o administrador esteja desconectada. Quando o usuário do Web client finalmente recebe sua mensagem de erro? Eu faço isso cerca de doze minutos depois.

Para que você não pense que este é apenas um exemplo tolo: vimos esse bug no código do cliente, embora muito pior do que eu descrevi aqui. No código do cliente em particular, a diferença entre a condição de erro e o fato de finalmente ser relatada ao usuário era várias semanas, porque muitas camadas estavam tentando novamente com esperas. Imagine o que aconteceria se houvesse dez tentativas em vez de três .

Geralmente, a coisa certa a fazer com uma condição de erro é relatá-la imediatamente e deixar que o usuário decida o que fazer. Se o usuário quiser criar uma política de novas tentativas automáticas, permita que elas criem essa política no nível apropriado na abstração do software.

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

Então você ligaria:

 TryThreeTimes(DoSomething); 

…ou alternativamente…

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

Uma opção mais flexível:

 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 usado como:

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

Uma versão mais moderna com suporte para async / await:

 public async Task DoWithRetryAsync(Func 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 usado como:

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

O bloco de aplicação de tratamento de falhas transitórias fornece uma coleção extensível de estratégias de repetição, incluindo:

  • Incremental
  • Intervalo fixo
  • Retorno exponencial

Também inclui uma coleção de estratégias de detecção de erros para serviços baseados em nuvem.

Para mais informações, consulte este capítulo do Guia do desenvolvedor.

Disponível via NuGet (procure por ‘ topaz ‘).

Permitindo funções e repetindo mensagens

 public static T RetryMethod(Func 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; } 

Você também pode considerar adicionar o tipo de exceção para o qual deseja tentar novamente. Por exemplo, esta é uma exceção de tempo limite que você deseja tentar novamente? Uma exceção de database?

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

Você também pode observar que todos os outros exemplos têm um problema semelhante ao testar novas tentativas == 0 e tentar novamente o infinito ou deixar de gerar exceções quando receber um valor negativo. Também Sleep (-1000) falhará nos blocos catch acima. Depende de quão “bobo” você espera que as pessoas sejam, mas a programação defensiva nunca é demais.

Sou fã de methods de recursion e extensão, então aqui estão meus dois centavos:

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

Com base no trabalho anterior, pensei em aprimorar a lógica de repetição de três maneiras:

  1. Especificando qual tipo de exceção para capturar / tentar novamente. Esse é o principal incentivo de que tentar novamente qualquer exceção é simplesmente errado.
  2. Não aninhando a última tentativa em um try / catch, alcançando um desempenho ligeiramente melhor
  3. Tornando-se um método de extensão Action

     static class ActionExtensions { public static void InvokeAndRetryOnException (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( ); } } 

O método pode então ser chamado assim (methods anônimos também podem ser usados, é claro):

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

Use Polly

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

Aqui está um novo genérico que eu uso com Polly

 public T Retry(Func action, int retryCount = 0) { PolicyResult policyResult = Policy .Handle() .Retry(retryCount) .ExecuteAndCapture(action); if (policyResult.Outcome == OutcomeType.Failure) { throw policyResult.FinalException; } return policyResult.Result; } 

Use assim

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

Eu implementaria isso:

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

Eu não usaria exceções da maneira como são usadas nos outros exemplos. Parece-me que, se esperamos a possibilidade de um método não ter sucesso, sua falha não é uma exceção. Portanto, o método que estou chamando deve retornar true se tiver êxito e false se falhar.

Por que é um Func e não apenas um Func ? Então, se eu quiser que um método seja capaz de lançar uma exceção na falha, eu tenho uma maneira de informar que esta é a última tentativa.

Então eu poderia usá-lo com código como:

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

Se passar um parâmetro que o método não usa se mostra estranho, é trivial implementar uma sobrecarga de Retry que apenas usa Func também.

Mantenha-o simples com o C # 6.0

 public async Task Retry(Func action, TimeSpan retryInterval, int retryCount) { try { return action(); } catch when (retryCount != 0) { await Task.Delay(retryInterval); return await Retry(action, retryInterval, --retryCount); } } 

Para aqueles que querem ter a opção de repetir em qualquer exceção ou definir explicitamente o tipo de exceção, use isto:

 public class RetryManager { public void Do(Action action, TimeSpan interval, int retries = 3) { Try(() => { action(); return null; }, interval, retries); } public T Do(Func action, TimeSpan interval, int retries = 3) { return Try( action , interval , retries); } public T Do(Func action, TimeSpan interval, int retries = 3) where E : Exception { return Try( action , interval , retries); } public void Do(Action action, TimeSpan interval, int retries = 3) where E : Exception { Try(() => { action(); return null; }, interval, retries); } private T Try(Func action, TimeSpan interval, int retries = 3) where E : Exception { var exceptions = new List(); 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); } } 

Implementou a resposta do LBushkin na última moda:

  public static async Task Do(Func task, TimeSpan retryInterval, int maxAttemptCount = 3) { var exceptions = new List(); 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 Do(Func> task, TimeSpan retryInterval, int maxAttemptCount = 3) { var exceptions = new List(); 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); } 

e usá-lo:

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

enquanto a function [TaskFunction] pode ser Task ou apenas Task .

Minha implementação async do método de repetição:

 public static async Task DoAsync(Func action, TimeSpan retryInterval, int retryCount = 3) { var exceptions = new List(); 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); } 

Pontos-chave: Eu usei .ConfigureAwait(false); e Func vez disso Func

O backoff exponencial é uma boa estratégia de repetição do que simplesmente tentar um número x de vezes. Você pode usar uma biblioteca como a Polly para implementá-la.

Eu precisava de um método que suporta o cancelamento, enquanto eu estava nisso, eu adicionei suporte para o retorno de falhas intermediárias.

 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(); 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 { private readonly IEnumerable failureExceptions; private readonly int failureCount; protected internal RetryResult( ICollection failureExceptions) { this.failureExceptions = failureExceptions; this.failureCount = failureExceptions.Count; } } public int FailureCount { get { return this.failureCount; } } public IEnumerator GetEnumerator() { return this.failureExceptions.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } 

Você pode usar a function Retry assim, tente novamente 3 vezes com um atraso de 10 segundos, mas sem cancelamento.

 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 tente novamente eternamente a cada cinco segundos, a menos que seja cancelado.

 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 você pode imaginar, no meu código-fonte eu sobrecarreguei a function Retry para suportar os diferentes tipos de delgate que eu gostaria de usar.

Ou que tal fazê-lo um pouco mais puro …

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

Acredito que lançar exceções deve geralmente ser evitado como um mecanismo, a menos que você as passe entre limites (como construir uma biblioteca que outras pessoas possam usar). Por que não apenas o comando DoSomething() retorna true se foi bem-sucedido e false outra forma?

EDIT: E isso pode ser encapsulado dentro de uma function como outros sugeriram também. O único problema é se você não está escrevendo a function DoSomething() você mesmo

Eu tive a necessidade de passar algum parâmetro para o meu método para tentar novamente e ter um valor de resultado; então eu preciso de uma expressão … Eu construo esta class que faz o trabalho (ela é inspirada na do LBushkin), você pode usá-la assim:

 static void Main(string[] args) { // one shot var res = Retry.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix); // delayed execute var retry = new Retry(() => 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 { Expression> _Method; int _NumRetries; TimeSpan _RetryTimeout; Action _OnFailureAction; public Retry(Expression> 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> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction) { var retry = new Retry(method, numRetries, retryTimeout, onFailureAction); return retry.Execute(); } } 

ps. a solução do LBushkin faz mais uma tentativa = D

Eu adicionaria o seguinte código à resposta aceita

 public static class Retry where TException : Exception //ability to pass the exception type { //same code as the accepted answer .... public static T Do(Func action, TimeSpan retryInterval, int retryCount = 3) { var exceptions = new List(); 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); } } 

Basicamente, o código acima está tornando a class Retry genérica para que você possa passar o tipo de exceção que deseja capturar para tentar novamente.

Agora use quase da mesma maneira, mas especificando o tipo de exceção

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

Eu sei que esta resposta é muito antiga, mas eu só queria comentar sobre isso, porque eu tive problemas ao usar essas instruções while with counters.

Ao longo dos anos, resolvi uma abordagem melhor, penso eu. Isso é usar algum tipo de agregação de events como extensões reativas “Assunto” ou algo parecido. Quando uma tentativa falha, você simplesmente publica um evento dizendo que a tentativa falhou e faz com que a function de agregador reprograme o evento. Isso permite muito mais controle sobre a nova tentativa sem poluir a chamada em si com um monte de repetições e quais não. Nem você está amarrando um único thread com um monte de thread dorme.

Faça isso simples em C #, Java ou outros idiomas:

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

e usá-lo em seu código muito simples:

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

ou você pode usá-lo em methods recursivos:

 void recursiveMethod(){ //some code if(ShouldRetryHandler.shouldRetry()){ recursiveMethod(); } //some code } 
 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); } } 

I’ve written a small class based on answers posted here. Hopefully it will help someone: https://github.com/natenho/resiliency

 using System; using System.Threading; ///  /// Classe utilitária para suporte a resiliência ///  public sealed class Resiliency { ///  /// Define o valor padrão de número de tentativas ///  public static int DefaultRetryCount { get; set; } ///  /// Define o valor padrão (em segundos) de tempo de espera entre tentativas ///  public static int DefaultRetryTimeout { get; set; } ///  /// Inicia a parte estática da resiliência, com os valores padrões ///  static Resiliency() { DefaultRetryCount = 3; DefaultRetryTimeout = 0; } ///  /// Executa uma  e tenta novamente DefaultRetryCount vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa. public static void Try(Action action) { Try(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Número de novas tentativas a serem realizadas /// Tempo de espera antes de cada nova tentativa public static void Try(Action action, int retryCount, TimeSpan retryTimeout) { Try(action, retryCount, retryTimeout, null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Número de novas tentativas a serem realizadas /// Tempo de espera antes de cada nova tentativa /// Permitindo manipular os critérios para realizar as tentativas public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action> tryHandler) { Try(action, retryCount, retryTimeout, tryHandler); } ///  /// Executa uma  e tenta novamente por até DefaultRetryCount vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Permitindo manipular os critérios para realizar as tentativas /// Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa. public static void Try(Action action, Action> tryHandler) { Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa. public static void Try(Action action) where TException : Exception { Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada ///  public static void Try(Action action, int retryCount) where TException : Exception { Try(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada ///  ///  public static void Try(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception { Try(action, retryCount, retryTimeout, null); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda qualquer  ///  /// Ação a ser realizada /// Permitindo manipular os critérios para realizar as tentativas /// Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa. public static void Try(Action action, Action> tryHandler) where TException : Exception { Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler); } ///  /// Executa uma  e tenta novamente determinado número de vezes quando for triggersda uma  definida no tipo genérico ///  /// Ação a ser realizada /// Número de novas tentativas a serem realizadas /// Tempo de espera antes de cada nova tentativa /// Permitindo manipular os critérios para realizar as tentativas /// Construído a partir de várias ideias no post  public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action> 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(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(); } } ///  /// Permite manipular o evento de cada tentativa da class de  ///  public class ResiliencyTryHandler where TException : Exception { #region Properties ///  /// Opção para abortar o ciclo de tentativas ///  public bool AbortRetry { get; set; } ///  ///  a ser tratada ///  public TException Exception { get; private set; } ///  /// Identifca o número da tentativa atual ///  public int CurrentTry { get; private set; } #endregion #region Constructors ///  /// Instancia um manipulador de tentativa. É utilizado internamente /// por  para permitir que o cliente altere o /// comportamento do ciclo de tentativas ///  public ResiliencyTryHandler(TException exception, int currentTry) { Exception = exception; CurrentTry = currentTry; } #endregion }