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?
Polly
.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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With