Sometimes I need to retry the operation several times before giving up. My code is as follows:
int retries = 3; while(true) { try { DoSomething(); break; // success! } catch { if(--retries == 0) throw; else Thread.Sleep(1000); } }
I want to rewrite it with the normal retry function as follows:
TryThreeTimes(DoSomething);
Is it OK in C? What is the code for the TryThreeTimes() method?
#1 building
I need to pass some parameters to my method to try again and get the result value; so I need an expression.. I've built a working class (inspired by LBushkin), 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. LBushkin solution retry again = D
#2 building
Transient fault handling application block Provides an extensible set of retry policies, including:
- Increased
- Fixed interval
- Exponential regression
It also includes a collection of error detection policies for cloud based services.
For more information, see the This chapter .
May pass NuGet get (search for "topaz").
#3 building
I need a way to support cancellation, during which 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 to Retry 3 times with a delay of 10 seconds, but do not cancel.
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 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 overloaded the Retry function to support the different types of proxies I wanted to use.
#4 building
I like recursion and extension methods, so this is my two cents:
public static void InvokeWithRetries(this Action @this, ushort numberOfRetries) { try { @this(); } catch { if (numberOfRetries == 0) throw; InvokeWithRetries(@this, --numberOfRetries); } }
#5 building
public delegate void ThingToTryDeletage(); public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime) { while(true) { try { ThingToTryDelegate(); } catch { if( --N == 0) throw; else Thread.Sleep(time); } }