Last active
August 29, 2015 14:08
-
-
Save icalvo/3f5602f7e868e7a00690 to your computer and use it in GitHub Desktop.
Retry logic, generic enough to manage lots of possible cases. As an example of its genericity, a reimplementation of one method of gist https://gist.github.com/gazlu/7886208. See my comment for details.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Retrier<TResult> { | |
public static TResult Try(Func<TResult> func, int maxRetries, TResult expectedValue, int delayInMilliseconds = 0) | |
{ | |
FunctionExecutor executor = new FunctionExecutor(func); | |
try | |
{ | |
GenericRetrier.Retry( | |
executor.Execute, | |
maxRetries, | |
() => executor.Result.Equals(expectedValue), | |
(ex, time) => Thread.Sleep(delayInMilliseconds)); | |
} | |
catch (AggregateException) | |
{ | |
} | |
return default(TResult); | |
} | |
private class FunctionExecutor | |
{ | |
private readonly Func<TResult> _func; | |
public FunctionExecutor(Func<TResult> func) | |
{ | |
_func = func; | |
} | |
public TResult Result { get; private set; } | |
public void Execute() | |
{ | |
Result = _func(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ResultCheckException : Exception | |
{ | |
public ResultCheckException(string message) | |
: base(message) | |
{ | |
} | |
} | |
/// <summary> | |
/// Utilities for retrying a block of code a number of times. | |
/// </summary> | |
public static class GenericRetrier | |
{ | |
/// <summary> | |
/// Retries the specified action the specified number of times. | |
/// The condition for retry is either throwing an exception in the action, | |
/// or failing the result check (if provided). | |
/// When retrying, it executes a handler (if provided). | |
/// </summary> | |
/// <param name="action">The action.</param> | |
/// <param name="resultChecker">The result checker.</param> | |
/// <param name="numberOfTimes">The number of times.</param> | |
/// <param name="retryExceptionHandler">The retry exception handler.</param> | |
/// <returns> | |
/// Result provided by the action. | |
/// </returns> | |
/// <exception cref="AggregateException">If the action to be invoked fails for more than <see cref="numberOfTimes" />.</exception> | |
public static void Retry(Action action, int numberOfTimes, Func<bool> resultChecker = null, | |
Action<Exception, int> retryExceptionHandler = null) | |
{ | |
List<Exception> exceptions = new List<Exception>(); | |
for (int i = 0; i < numberOfTimes; i++) | |
{ | |
try | |
{ | |
action(); | |
if (resultChecker == null || resultChecker()) | |
{ | |
return; | |
} | |
throw new ResultCheckException("The action result check has failed"); | |
} | |
catch (Exception ex) | |
{ | |
if (i < numberOfTimes - 1) | |
{ | |
if (retryExceptionHandler != null) | |
{ | |
try | |
{ | |
retryExceptionHandler(ex, i + 1); | |
} | |
catch (Exception) | |
{ | |
} | |
} | |
} | |
exceptions.Add(ex); | |
} | |
} | |
throw new AggregateException(exceptions); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Rationales:
retryExceptionHandler
only works when you are going to retry: if you are out of attempts,Retry
has failed, so it will launch anAggregateException
and you can manage that case outside.retryExceptionHandler
: I didn't want to add this exception to the finalAggregateException
so that it containsnumberOfTimes
exceptions. If you need this exception and don't matter about matching the attempt # and the exceptions it caused, you could add the exception to the list. If you need both things, you will need a more complex structure; for example you could create anAggregateException
with the exceptions caused byaction
andretryExceptionHandler
and add it to the list.AggregateException
: nice way to provide to the client all the exceptions for each of the failures.ResultCheckerException
: this way all the failures (exception and result checker failures) are treated the same way (with an exception).