Reputation: 13111
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();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
I would like to rewrite this in a general retry function like:
TryThreeTimes(DoSomething);
Is it possible in C#? What would be the code for the TryThreeTimes()
method?
Upvotes: 550
Views: 387231
Reputation: 2552
There are many good answers already, but most of them should be improved by not rethrowing at the end, instead use catch (...) when ...
(since C# 6)
Exception filters are preferable to catching and rethrowing because they leave the stack unharmed.
/// <summary>
/// Retries an async action when a specific exception occurs, on last try the exception is not catched
/// </summary>
public static async Task RetryAsync<T>(Func<Task> action, int tries = 3, int sleep = 0, ILogger? logger = null) where T : Exception
{
while (true)
{
try
{
await action();
return;
}
catch (T ex) when (--tries > 0)
{
logger?.LogError(ex, "{Message}\nRetrying ({tries} remaining)...", ex.Message, tries);
await Task.Delay(sleep);
}
}
}
/// <summary>
/// Retries an action when a specific exception occurs, on last try the exception is not catched
/// </summary>
public static void Retry<T>(Action action, int tries = 3, int sleep = 0, ILogger? logger = null) where T : Exception
{
while (true)
{
try
{
action();
return;
}
catch (T ex) when (--tries > 0)
{
logger?.LogError(ex, "{Message}\nRetrying ({tries} remaining)...", ex.Message, tries);
Thread.Sleep(sleep);
}
}
}
//TODO: we need additional versions to return results
Upvotes: 0
Reputation: 2597
It is also possible to use a recursive retry, by simply returning itself with an attempt counter when you need to retry. Then you need two extra parameters, one for the attempt counter, and one for the number of max attempts. Remember to increase the attempt counter upon retry. If you use an interface, you can choose to add the parameters to the interface, or add a wrapper method that calls the method with those parameters. Here is an example of the latter.
public interface ISomeService
{
Task TryThreeTimes();
}
public class SomeService : ISomeService
{
public async Task TryTreeTimes() {
return await TryTreeTimes(attempt = 0, maxAttempt = 3);
}
private void TryTreeTimes(int attempt = 0, int maxAttempt = 3)
{
try
{
// execute code
}
catch (Exception exception)
{
// handle exception
var needToRetry = /*some condition*/ true;
if (needToRetry && attempt < maxAttempt)
{
await Task.Delay(1000);
return TryTreeTimes(attempt + 1, maxAttempt);
}
}
}
}
Make sure to handle any disposable variables properly to avoid memory leak, for instance with a finally block at the end. Remember that this will recursively add data to the stack, so be advised to balance memory usage with max possible attempts.
Upvotes: 0
Reputation: 3001
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, _ => TimeSpan.FromSeconds(3))
.Execute(DoSomething);
Upvotes: 285
Reputation: 836
I have two implementations of this pattern using Polly. One is async.
My synchronous method is based on this answer by Erik Bergstedt
public static T Retry<T>(Func<T> action, TimeSpan retryWait, int retryCount = 0)
{
PolicyResult<T> policyResult = Policy
.Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
.WaitAndRetry(retryCount, retryAttempt => retryWait)
.ExecuteAndCapture(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
Async:
public static async Task<T> RetryAsync<T>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0)
{
PolicyResult<T> policyResult = await Policy
.Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
.ExecuteAndCaptureAsync(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
It would also be easy to allow an exception type to be passed in as well as the lambda for the exception type.
Upvotes: 5
Reputation: 659956
This is possibly a bad idea. First, it is emblematic of the maxim "the definition of insanity is doing the same thing twice and expecting different results each time". Second, this coding pattern does not compose well with itself. For example:
Suppose your network hardware layer resends a packet three times on failure, waiting, say, a second between failures.
Now suppose the software layer resends a notification about a failure three times on packet failure.
Now suppose the notification layer reactivates the notification three times on a notification delivery failure.
Now suppose the error reporting layer reactivates the notification layer three times on a notification failure.
And now suppose the web server reactivates the error reporting three times on error failure.
And now suppose the web client resends the request three times upon getting an error from the server.
Now suppose the line on the network switch that is supposed to route the notification to the administrator is unplugged. When does the user of the web client finally get their error message? I make it at about twelve minutes later.
Lest you think this is just a silly example: we have seen this bug in customer code, though far, far worse than I've described here. In the particular customer code, the gap between the error condition happening and it finally being reported to the user was several weeks because so many layers were automatically retrying with waits. Just imagine what would happen if there were ten retries instead of three.
Usually the right thing to do with an error condition is report it immediately and let the user decide what to do. If the user wants to create a policy of automatic retries, let them create that policy at the appropriate level in the software abstraction.
Upvotes: 59
Reputation: 124
I've implemented an async version of the accepted answer like so - and it seems to work nicely - any comments?
public static async Task DoAsync(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
DoAsync<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static async Task<T> DoAsync<T>(
Func<Task<T>> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return await action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
And, call it simply like this:
var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);
Upvotes: -1
Reputation: 4548
Here's an async
/await
version that aggregates exceptions and supports cancellation.
/// <seealso href="https://learn.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 );
}
Upvotes: 1
Reputation: 2138
Update after 6 years: now I consider that the approach below is pretty bad. To create a retry logic we should consider to use a library like Polly.
My async
implementation of the retry method:
public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
await Task.Delay(retryInterval).ConfigureAwait(false);
}
throw new AggregateException(exceptions);
}
Key points: I used .ConfigureAwait(false);
and Func<dynamic>
instead Func<T>
Upvotes: 4
Reputation: 61719
This method allows retries on certain exception types (throws others immediately).
public static void DoRetry(
List<Type> retryOnExceptionTypes,
Action actionToTry,
int retryCount = 5,
int msWaitBeforeEachRety = 300)
{
for (var i = 0; i < retryCount; ++i)
{
try
{
actionToTry();
break;
}
catch (Exception ex)
{
// Retries exceeded
// Throws on last iteration of loop
if (i == retryCount - 1) throw;
// Is type retryable?
var exceptionType = ex.GetType();
if (!retryOnExceptionTypes.Contains(exceptionType))
{
throw;
}
// Wait before retry
Thread.Sleep(msWaitBeforeEachRety);
}
}
}
public static void DoRetry(
Type retryOnExceptionType,
Action actionToTry,
int retryCount = 5,
int msWaitBeforeEachRety = 300)
=> DoRetry(new List<Type> {retryOnExceptionType}, actionToTry, retryCount, msWaitBeforeEachRety);
Example usage:
DoRetry(typeof(IOException), () => {
using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
{
fs.Write(entryBytes, 0, entryBytes.Length);
}
});
Upvotes: 3
Reputation: 59
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RetryHelper {
private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
private int retryWaitInMS;
private int maxRetries;
public RetryHelper() {
this.retryWaitInMS = 300;
this.maxRetries = 3;
}
public RetryHelper(int maxRetry) {
this.maxRetries = maxRetry;
this.retryWaitInMS = 300;
}
public RetryHelper(int retryWaitInSeconds, int maxRetry) {
this.retryWaitInMS = retryWaitInSeconds;
this.maxRetries = maxRetry;
}
public <T> T retryAndReturn(Supplier<T> supplier) {
try {
return supplier.get();
} catch (Exception var3) {
return this.retrySupplier(supplier);
}
}
public void retry(Runnable runnable) {
try {
runnable.run();
} catch (Exception var3) {
this.retrySupplier(() -> {
runnable.run();
return null;
});
}
}
private <T> T retrySupplier(Supplier<T> supplier) {
log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
int retryCounter = 0;
while(retryCounter < this.maxRetries) {
try {
return supplier.get();
} catch (Exception var6) {
++retryCounter;
log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
if (retryCounter >= this.maxRetries) {
log.error("Max retries exceeded.");
throw var6;
}
try {
Thread.sleep((long)this.retryWaitInMS);
} catch (InterruptedException var5) {
var5.printStackTrace();
}
}
}
return supplier.get();
}
public int getRetryWaitInMS() {
return this.retryWaitInMS;
}
public int getMaxRetries() {
return this.maxRetries;
}
}
try {
returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
//or no return type:
new RetryHelper().retry(() -> mytask(args));
} catch(Exception ex){
log.error(e.getMessage());
throw new CustomException();
}
Upvotes: -2
Reputation: 35696
I needed a method that supports cancellation, while I was at it, I added support for returning intermediate failures.
public static class ThreadUtils
{
public static RetryResult Retry(
Action target,
CancellationToken cancellationToken,
int timeout = 5000,
int retries = 0)
{
CheckRetryParameters(timeout, retries)
var failures = new List<Exception>();
while(!cancellationToken.IsCancellationRequested)
{
try
{
target();
return new RetryResult(failures);
}
catch (Exception ex)
{
failures.Add(ex);
}
if (retries > 0)
{
retries--;
if (retries == 0)
{
throw new AggregateException(
"Retry limit reached, see InnerExceptions for details.",
failures);
}
}
if (cancellationToken.WaitHandle.WaitOne(timeout))
{
break;
}
}
failures.Add(new OperationCancelledException(
"The Retry Operation was cancelled."));
throw new AggregateException("Retry was cancelled.", failures);
}
private static void CheckRetryParameters(int timeout, int retries)
{
if (timeout < 1)
{
throw new ArgumentOutOfRangeException(...
}
if (retries < 0)
{
throw new ArgumentOutOfRangeException(...
}
}
public class RetryResult : IEnumerable<Exception>
{
private readonly IEnumerable<Exception> failureExceptions;
private readonly int failureCount;
protected internal RetryResult(
ICollection<Exception> failureExceptions)
{
this.failureExceptions = failureExceptions;
this.failureCount = failureExceptions.Count;
}
}
public int FailureCount
{
get { return this.failureCount; }
}
public IEnumerator<Exception> GetEnumerator()
{
return this.failureExceptions.GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
You can use the Retry
function like this, retry 3 times with a 10 second delay but without cancellation.
try
{
var result = ThreadUtils.Retry(
SomeAction,
CancellationToken.None,
10000,
3);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// oops, 3 retries wasn't enough.
}
Or, retry eternally every five seconds, unless cancelled.
try
{
var result = ThreadUtils.Retry(
SomeAction,
someTokenSource.Token);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// operation was cancelled before success.
}
As you can guess, In my source code I've overloaded the Retry
function to support the differing delgate types I desire to use.
Upvotes: 3
Reputation: 10895
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
.
Upvotes: 13
Reputation: 310792
public void TryThreeTimes(Action action)
{
var tries = 3;
while (true) {
try {
action();
break; // success!
} catch {
if (--tries == 0)
throw;
Thread.Sleep(1000);
}
}
}
Then you would call:
TryThreeTimes(DoSomething);
...or alternatively...
TryThreeTimes(() => DoSomethingElse(withLocalVariable));
A more flexible option:
public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
action();
break; // success!
} catch {
if (--tryCount == 0)
throw;
Thread.Sleep(sleepPeriod);
}
}
}
To be used as:
DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);
A more modern version with support for async/await:
public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
await action();
return; // success!
} catch {
if (--tryCount == 0)
throw;
await Task.Delay(sleepPeriod);
}
}
}
To be used as:
await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
Upvotes: 78
Reputation: 131646
Blanket catch statements that simply retry the same call can be dangerous if used as a general exception handling mechanism. Having said that, here's a lambda-based retry wrapper that you can use with any method. I chose to factor the number of retries and the retry timeout out as parameters for a bit more flexibility:
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
You can now use this utility method to perform retry logic:
Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
or:
Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));
or:
int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);
Or you could even make an async
overload.
Upvotes: 646
Reputation: 234
Keep it simple with C# 6.0
public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
try
{
return action();
}
catch when (retryCount != 0)
{
await Task.Delay(retryInterval);
return await Retry(action, retryInterval, --retryCount);
}
}
Upvotes: 9
Reputation: 5591
I've written a small class based on answers posted here. Hopefully it will help someone: https://github.com/natenho/resiliency
using System;
using System.Threading;
/// <summary>
/// Classe utilitária para suporte a resiliência
/// </summary>
public sealed class Resiliency
{
/// <summary>
/// Define o valor padrão de número de tentativas
/// </summary>
public static int DefaultRetryCount { get; set; }
/// <summary>
/// Define o valor padrão (em segundos) de tempo de espera entre tentativas
/// </summary>
public static int DefaultRetryTimeout { get; set; }
/// <summary>
/// Inicia a parte estática da resiliência, com os valores padrões
/// </summary>
static Resiliency()
{
DefaultRetryCount = 3;
DefaultRetryTimeout = 0;
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
public static void Try(Action action)
{
Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
{
Try<Exception>(action, retryCount, retryTimeout, null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
{
Try<Exception>(action, retryCount, retryTimeout, tryHandler);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
{
Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try<TException>(Action action) where TException : Exception
{
Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount"></param>
public static void Try<TException>(Action action, int retryCount) where TException : Exception
{
Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount"></param>
/// <param name="retryTimeout"></param>
public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
{
Try<TException>(action, retryCount, retryTimeout, null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
{
Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
{
if (action == null)
throw new ArgumentNullException(nameof(action));
while (retryCount-- > 0)
{
try
{
action();
return;
}
catch (TException ex)
{
//Executa o manipulador de exception
if (tryHandler != null)
{
var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
tryHandler(callback);
//A propriedade que aborta pode ser alterada pelo cliente
if (callback.AbortRetry)
throw;
}
//Aguarda o tempo especificado antes de tentar novamente
Thread.Sleep(retryTimeout);
}
}
//Na última tentativa, qualquer exception será lançada de volta ao chamador
action();
}
}
/// <summary>
/// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
/// </summary>
public class ResiliencyTryHandler<TException> where TException : Exception
{
#region Properties
/// <summary>
/// Opção para abortar o ciclo de tentativas
/// </summary>
public bool AbortRetry { get; set; }
/// <summary>
/// <see cref="Exception"/> a ser tratada
/// </summary>
public TException Exception { get; private set; }
/// <summary>
/// Identifca o número da tentativa atual
/// </summary>
public int CurrentTry { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Instancia um manipulador de tentativa. É utilizado internamente
/// por <see cref="Resiliency"/> para permitir que o cliente altere o
/// comportamento do ciclo de tentativas
/// </summary>
public ResiliencyTryHandler(TException exception, int currentTry)
{
Exception = exception;
CurrentTry = currentTry;
}
#endregion
}
Upvotes: -2
Reputation: 847
For those who want to have both the option to retry on any exception or explicitly set the exception type, use this:
public class RetryManager
{
public void Do(Action action,
TimeSpan interval,
int retries = 3)
{
Try<object, Exception>(() => {
action();
return null;
}, interval, retries);
}
public T Do<T>(Func<T> action,
TimeSpan interval,
int retries = 3)
{
return Try<T, Exception>(
action
, interval
, retries);
}
public T Do<E, T>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
return Try<T, E>(
action
, interval
, retries);
}
public void Do<E>(Action action,
TimeSpan interval,
int retries = 3) where E : Exception
{
Try<object, E>(() => {
action();
return null;
}, interval, retries);
}
private T Try<T, E>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
var exceptions = new List<E>();
for (int retry = 0; retry < retries; retry++)
{
try
{
if (retry > 0)
Thread.Sleep(interval);
return action();
}
catch (E ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
Upvotes: 2
Reputation: 126
Exponential backoff is a good retry strategy than simply trying x number of times. You can use a library like Polly to implement it.
Upvotes: 2
Reputation: 49
int retries = 3;
while (true)
{
try
{
//Do Somthing
break;
}
catch (Exception ex)
{
if (--retries == 0)
return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
else
System.Threading.Thread.Sleep(100);
}
Upvotes: -1
Reputation: 7515
Do it simple in C#, Java or other languages:
internal class ShouldRetryHandler {
private static int RETRIES_MAX_NUMBER = 3;
private static int numberTryes;
public static bool shouldRetry() {
var statusRetry = false;
if (numberTryes< RETRIES_MAX_NUMBER) {
numberTryes++;
statusRetry = true;
//log msg -> 'retry number' + numberTryes
}
else {
statusRetry = false;
//log msg -> 'reached retry number limit'
}
return statusRetry;
}
}
and use it in your code very simple:
void simpleMethod(){
//some code
if(ShouldRetryHandler.shouldRetry()){
//do some repetitive work
}
//some code
}
or you can use it in recursive methods:
void recursiveMethod(){
//some code
if(ShouldRetryHandler.shouldRetry()){
recursiveMethod();
}
//some code
}
Upvotes: 0
Reputation: 868
I know this answer is very old but I just wanted to comment on this because I have run into issues using these while, do, whatever statement with counters.
Over the years I have settled on a better approach I think. That is to use some sort of event aggregation like a reactive extensions "Subject" or the like. When a try fails, you simply publish an event saying the try failed, and have the aggregator function re-schedule the event. This allows you much more control over the retry without polluting the call itself with a bunch of retry loops and what not. Nor are you tying up a single thread with a bunch of thread sleeps.
Upvotes: 0
Reputation: 912
Use Polly
https://github.com/App-vNext/Polly-Samples
Here is a retry-generic I use with Polly
public T Retry<T>(Func<T> action, int retryCount = 0)
{
PolicyResult<T> policyResult = Policy
.Handle<Exception>()
.Retry(retryCount)
.ExecuteAndCapture<T>(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
Use it like this
var result = Retry(() => MyFunction()), 3);
Upvotes: 9
Reputation: 64218
You might also consider adding the exception type you want to retry for. For instance is this a timeout exception you want to retry? A database exception?
RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);
public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
if (action == null)
throw new ArgumentNullException("action");
if (retryOnExceptionType == null)
throw new ArgumentNullException("retryOnExceptionType");
while (true)
{
try
{
action();
return;
}
catch(Exception e)
{
if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
throw;
if (retryTimeout > 0)
System.Threading.Thread.Sleep(retryTimeout);
}
}
}
You might also note that all of the other examples have a similar issue with testing for retries == 0 and either retry infinity or fail to raise exceptions when given a negative value. Also Sleep(-1000) will fail in the catch blocks above. Depends on how 'silly' you expect people to be but defensive programming never hurts.
Upvotes: 14
Reputation: 6981
I would add the following code to the accepted answer
public static class Retry<TException> where TException : Exception //ability to pass the exception type
{
//same code as the accepted answer ....
public static T Do<T>(Func<T> action, TimeSpan retryInterval, int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
return action();
}
catch (TException ex) //Usage of the exception type
{
exceptions.Add(ex);
Thread.Sleep(retryInterval);
}
}
throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
}
}
Basically the above code is making the Retry
class generic so you can pass the type of the exception you want to catch for retry.
Now use it almost in the same way but specifying the exception type
Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
Upvotes: 0
Reputation: 2491
Building on the previous work, I thought about enhancing the retry logic in three ways:
Making it an Action
extension method
static class ActionExtensions
{
public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
{
if (action == null)
throw new ArgumentNullException("action");
while( retries-- > 0 )
{
try
{
action( );
return;
}
catch (T)
{
Thread.Sleep( retryDelay );
}
}
action( );
}
}
The method can then be invoked like so (anonymous methods can be used as well, of course):
new Action( AMethodThatMightThrowIntermittentException )
.InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
Upvotes: 7
Reputation: 4047
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);
}
}
Upvotes: 18
Reputation: 4107
The Transient Fault Handling Application Block provides an extensible collection of retry strategies including:
It also includes a collection of error detection strategies for cloud-based services.
For more information see this chapter of the Developer's Guide.
Available via NuGet (search for 'topaz').
Upvotes: 34
Reputation: 821
I had the need to pass some parameter to my method to retry, and have a result value; so i need an expression.. I build up this class that does the work (it is inspired to the the LBushkin's one) you can use it like this:
static void Main(string[] args)
{
// one shot
var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
// delayed execute
var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
var res2 = retry.Execute();
}
static void fix()
{
Console.WriteLine("oh, no! Fix and retry!!!");
}
static string retryThis(string tryThis)
{
Console.WriteLine("Let's try!!!");
throw new Exception(tryThis);
}
public class Retry<TResult>
{
Expression<Func<TResult>> _Method;
int _NumRetries;
TimeSpan _RetryTimeout;
Action _OnFailureAction;
public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
_Method = method;
_NumRetries = numRetries;
_OnFailureAction = onFailureAction;
_RetryTimeout = retryTimeout;
}
public TResult Execute()
{
TResult result = default(TResult);
while (_NumRetries > 0)
{
try
{
result = _Method.Compile()();
break;
}
catch
{
_OnFailureAction();
_NumRetries--;
if (_NumRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(_RetryTimeout);
}
}
return result;
}
public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
return retry.Execute();
}
}
ps. the LBushkin's solution does one more retry =D
Upvotes: 0
Reputation: 1894
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;
}
Upvotes: 16
Reputation: 96702
I'd implement this:
public static bool Retry(int maxRetries, Func<bool, bool> method)
{
while (maxRetries > 0)
{
if (method(maxRetries == 1))
{
return true;
}
maxRetries--;
}
return false;
}
I wouldn't use exceptions the way they're used in the other examples. It seems to me that if we're expecting the possibility that a method won't succeed, its failure isn't an exception. So the method I'm calling should return true if it succeeded, and false if it failed.
Why is it a Func<bool, bool>
and not just a Func<bool>
? So that if I want a method to be able to throw an exception on failure, I have a way of informing it that this is the last try.
So I might use it with code like:
Retry(5, delegate(bool lastIteration)
{
// do stuff
if (!succeeded && lastIteration)
{
throw new InvalidOperationException(...)
}
return succeeded;
});
or
if (!Retry(5, delegate(bool lastIteration)
{
// do stuff
return succeeded;
}))
{
Console.WriteLine("Well, that didn't work.");
}
If passing a parameter that the method doesn't use proves to be awkward, it's trivial to implement an overload of Retry
that just takes a Func<bool>
as well.
Upvotes: 4