Bom dia, Ricardo. Tudo bom?
Eu criei um exemplo para te mostrar.
Antes de tudo, desta Task resiliente, precisamos saber:
Para representar isto, criei a struct RetryOptions
:
public struct RetryOptions
{
public TimeSpan Interval { get; }
public int MaxRetries { get; }
public RetryOptions(TimeSpan interval, int maxRetries)
{
Interval = interval;
MaxRetries = maxRetries;
}
public static RetryOptions Default => new RetryOptions(TimeSpan.FromSeconds(5), 3);
}
O construtor recebe um TimeSpan
para o intervalo e um inteiro para o número de tentativas. Também criei um Default, com intervalo de 5 segundos e máximo de 3 tentativas.
Para a construção da task resiliente, criei a classe TaskUtils
:
public static class TaskUtils
{
public static Task CreateResilient(Action action, RetryOptions retryOptions)
{
return new Task(() =>
{
var tries = retryOptions.MaxRetries;
while (true)
{
try
{
action();
break;
}
catch
{
tries--;
if (tries <= 0)
throw;
Thread.Sleep(retryOptions.Interval);
}
}
});
}
public static Task CreateResilient(Action action) => CreateResilient(action, RetryOptions.Default);
}
O método CreateResilient(Action, RetryOptions)
recebe como argumento a Action
a ser executada e as opções. O que este método faz é criar uma Task
que executa um laço while(true)
com um bloco try/catch
. O laço é abandonado quando a action()
é executada corretamente ou quando o contador de tentativas tries
chega a zero.
No bloco catch
estamos decrementando o contador de tentativas e esperando o intervalo definido nas opções Thread.Sleep(retryOptions.Interval)
Criei um caso de exemplo, com a classe LancadorExcecoes
:
public class LancadorExcecoes
{
private int _numeroExcecoes;
public LancadorExcecoes(int quantidade)
{
_numeroExcecoes = quantidade;
}
public void Executar()
{
if (_numeroExcecoes > 0)
{
Console.WriteLine("Lançando exceção...");
_numeroExcecoes--;
throw new Exception("Testando resiliência");
}
else
{
Console.WriteLine("Executando sem lançar exceção.");
}
}
}
Esta classe não faz nada além de lançar uma exceção no método Executar
em _numeroExcecoes
chamadas.
E agora, juntando as peças, em uma aplicação Console:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Executando...");
var classeTeste = new LancadorExcecoes(quantidade: 10);
var task = TaskUtils.CreateResilient(() => classeTeste.Executar());
task
.ContinueWith((e) =>
{
if (e.IsFaulted)
{
Console.WriteLine("Ops. Exceção: " + e.Exception.Message);
}
else
{
Console.WriteLine("Executou sem problemas!");
}
});
task.Start();
Console.ReadLine();
}
}
Nessa execução, teremos a saída:
Executando...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Ops. Exceçao: One or more errors occurred. (Testando resiliência)
Mas, se alterarmos as opções de retry para 11 tentativas var task = TaskUtils.CreateResilient(() => classeTeste.Executar(), new RetryOptions(TimeSpan.FromSeconds(1), 11));
teremos a saída:
Executando...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Executando sem lançar exceção.
Executou sem problemas!
Mas, até agora, não temos o retorno dessa task! Com poucas alterações, podemos criar uma sobrecarga de CreateResilient
com Task de retorno! (fica na próxima resposta pra não estourar o limite de 5000 caracteres).