4
respostas

Retry

Olá parabéns pelo curso. Apenas senti falta de um cenário que seria interessante ter no curso. Se houvesse uma explicação de um tentativa de retry. Ex: Onde na consolidação de uma das contas da lista gerasse uma excessão, após X segundos reprocessa-la novamente.

4 respostas

Fala ai Ricardo, de boa cara ?

Legal esse ponto que você notou, vamos ver o que podemos fazer, acredito que em outro curso eles mostrem isso, mas só para garantir vou dar uma averiguada aqui :D

Obrigado Matheus. Fico no aguardo sobre alguma sugestão.

Bom dia, Ricardo. Tudo bom?

Eu criei um exemplo para te mostrar.

Antes de tudo, desta Task resiliente, precisamos saber:

  • Intervalo entre as execuções da ação com exceção;

  • Número de tentativas de execução;

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

Continuando...

public static Task<TResult> CreateResilient<TResult>(Func<TResult> action, RetryOptions retryOptions)
{
    return new Task<TResult>(() =>
    {
        var tries = retryOptions.MaxRetries;

        while (true)
        {
            try
            {
                return action();
            }
            catch
            {
                tries--;
                if (tries <= 0)
                    throw;

                Thread.Sleep(retryOptions.Interval);
            }
        }
    });
}

E, usando-a:

var excecoesRestantes = 3;
var taskComRetorno = TaskUtils.CreateResilient(() =>
{
    if (excecoesRestantes > 0)
    {
        excecoesRestantes--;
        Console.WriteLine("Lançando exceção...");
        throw new Exception("Exceção!");
    }

    return "Ahá, eu sou o retorno da Task resiliente!";
}, new RetryOptions(TimeSpan.FromSeconds(0), 4));


taskComRetorno.ContinueWith((e) =>
 {
     if (e.IsFaulted)
     {
         Console.WriteLine("Ops. Exceção: " + e.Exception.Message);
     }
     else
     {
         Console.WriteLine($"Executou sem problemas! Com o retorno '{e.Result}'");
     }
 });

taskComRetorno.Start();

Com a saída:

Executando...
Lançando exceçao...
Lançando exceçao...
Lançando exceçao...
Executou sem problemas! Com o retorno 'Ahá, eu sou o retorno da Task resiliente!'

O que você acha dessas implementações? Ficou com alguma dúvida?

Abs.