Occasionally I have a need to retry an operation several times before giving up. My code is like:
int retries = 3;
while(true) {
try {
DoSomething();
Or how about doing it a bit neater....
int retries = 3;
while (retries > 0)
{
if (DoSomething())
{
retries = 0;
}
else
{
retries--;
}
}
I believe throwing exceptions should generally be avoided as a mechanism unless your a passing them between boundaries (such as building a library other people can use). Why not just have the DoSomething()
command return true
if it was successful and false
otherwise?
EDIT: And this can be encapsulated inside a function like others have suggested as well. Only problem is if you are not writing the DoSomething()
function yourself
Implemented LBushkin's answer in the latest fashion:
public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
await task();
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
return await task();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
and to use it:
await Retry.Do([TaskFunction], retryInterval, retryAttempts);
whereas the function [TaskFunction]
can either be Task<T>
or just Task
.
Allowing for functions and retry messages
public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
Guard.IsNotNull(method, "method");
T retval = default(T);
do
{
try
{
retval = method();
return retval;
}
catch
{
onFailureAction();
if (numRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(retryTimeout);
}
} while (numRetries-- > 0);
return retval;
}
You should try Polly. It's a .NET library written by me that allows developers to express transient exception handling policies such as Retry, Retry Forever, Wait and Retry or Circuit Breaker in a fluent manner.
Policy
.Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
.Execute(() => DoSomething());
I'm a fan of recursion and extension methods, so here are my two cents:
public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
try
{
@this();
}
catch
{
if (numberOfRetries == 0)
throw;
InvokeWithRetries(@this, --numberOfRetries);
}
}
Here's an async
/await
version that aggregates exceptions and supports cancellation.
/// <seealso href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry"/>
protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
{
var exceptions = new List<Exception>();
for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
try {
return await action().ConfigureAwait( false );
} catch ( Exception ex ) {
exceptions.Add( ex );
if ( retries < maxRetries )
await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
else
throw new AggregateException( "Retry limit reached", exceptions );
}
exceptions.Add( new OperationCanceledException( cancelToken ) );
throw new AggregateException( "Retry loop was canceled", exceptions );
}