Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic async Retry with Timeout function in C#

I have hundreds of calls to various external API's that I want to wrap in a generic async function that is capable of doing retries and handling timeouts.

Basically, I think I need to implement something like this function call:

await Retry(()=>someFunctionAsync(doWorkParams, new CancellationToken()), retryCount, timeout);

How do I define such Retry function? Also, how do I call this function from Sync code, as a ton of my calls are housed in the sync methods?

like image 594
Igorek Avatar asked Mar 31 '17 19:03

Igorek


3 Answers

Don't reinvent the wheel. Just use Polly which supports exactly the scenario you're talking about as well as all kinds of scenarios for retry and advanced patterns like circuit breaker.

Here's an async example from their docs:

await Policy
  .Handle<SqlException>(ex => ex.Number == 1205)
  .Or<ArgumentException>(ex => ex.ParamName == "example")
  .RetryAsync()
  .ExecuteAsync(() => DoSomethingAsync());
like image 123
Jesse Carter Avatar answered Nov 17 '22 13:11

Jesse Carter


How do I define such Retry function?

  1. Install Polly.
  2. Define a policy and use it.
  3. Profit.

Something like this should work:

static async Task RetryAsync(Func<CancellationToken, Task> func, int retryCount, TimeSpan timeout)
{
  using (var cts = new CancellationTokenSource(timeout))
  {
    var policy = Policy.Handle<Exception>(ex => !(ex is OperationCanceledException))
        .RetryAsync(retryCount);
    await policy.ExecuteAsync(() => func(cts.Token)).ConfigureAwait(false);
  }
}

Also, how do I call this function from Sync code, as a ton of my calls are housed in the sync methods?

That's a totally different question. Blocking on asynchronous code always has pitfalls. There is no solution that works for any arbitrary Func<Task>. See my MSDN article for a variety of hacks you can try, if you must do this. It would be better, though, to keep asynchronous code asynchronous, and synchronous code synchronous.

like image 28
Stephen Cleary Avatar answered Nov 17 '22 12:11

Stephen Cleary


If you are still curious about how to do it without Policy it would be something like this:

/// Untested code
static class Retry
{
    public static async Task<T> Run<T>(Func<CancellationToken, Task<T>> function, int retries, TimeSpan timeout)
    {
        Exception error = null;
        do
        {
            try
            {
                var cancellation = new CancellationTokenSource(timeout);
                return await function(cancellation.Token).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                error = ex;
            }

            retries--;
        }
        while (retries > 0);

        throw error;
    }

    public static async Task<T> Run<T>(Func<Task<T>> function, int retries, TimeSpan timeout)
    {
        Exception error = null;
        do
        {
            try
            {
                var timeoutTask = Task.Delay(timeout);
                var resultTask = function();

                await Task.WhenAny(resultTask, timeoutTask).ConfigureAwait(false);

                if (resultTask.Status == TaskStatus.RanToCompletion)
                    return resultTask.Result;
                else
                    error = new TimeoutException();
            }
            catch (Exception ex)
            {
                error = ex;
            }

            retries--;
        }
        while (retries > 0);

        throw error;
    }
}
like image 3
vtortola Avatar answered Nov 17 '22 13:11

vtortola