简体   繁体   中英

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?

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM