6

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?

3 Answers 3

12

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());
Sign up to request clarification or add additional context in comments.

Comments

10

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.

4 Comments

Thank you and sorry for sounding stupid.. but I dont see how your example will properly cancel my long-running async calls, like web requests. Does it automatically handle cancellation tokens to the func() if that func() supports them?
If you need to cancel them, you can pass a CancellationToken in yourself: await RetryAsync(() => DoSomethingAsync(token), 5, TimeSpan.FromSeconds(1));
Sorry, I didnt explain correctly., I meant that if the method times out and Polly decides to cancel it, how will it do that without cancellation token? I dont need to cancel methods from outside, however, I need them cancelled properly when Timeout is reached.
Ah, I assumed that parameter was the time to wait between retries. See my edit for how to do timeout-based cancellation.
4

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

3 Comments

Shouldn't you throw the error only when it is not null. Nice try but point with Polly is the level features rolled up in the Fluent Syntax
If it gets to the throw, error is not null. It needs to replace the while with a do/while to be correct though.
The question is not about Polly, it is about how to do a retry function. Although I recognize Polly's value, it is interesting to know how to do it by hand as well :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.