Support for retry for action methods. One class RetryHelper
that supports retry for four scenarios of action/function signature:
- Actions/functions with zero argument, and void return type.
- Actions/functions with zero argument, and generic type output
- Actions/functions with one argument, and generic type output.
public static class RetryHelper
{
public static void InvokeActionWithRetry(IActionRetryOptions options)
{
uint retries = 0;
do
{
try
{
options.Action();
break;
}
catch
{
//log
}
retries++;
Thread.Sleep(options.BackOffInterval);
} while (retries < options.RetryAttempts);
}
public static void InvokeActionWithRetry<TResult>(IActionRetryOptions<TResult> options)
{
uint retries = 0;
do
{
try
{
options.Result = options.InvokeAction();
if (options.Success)
break;
}
catch
{
//log
}
retries++;
Thread.Sleep(options.BackOffInterval);
} while (retries < options.RetryAttempts);
}
public static void InvokeActionWithRetry<TIn, TResult> (IActionRetryOptions<TIn, TResult> options)
{
uint retries = 0;
do
{
try
{
options.Result = options.InvokeAction();
if (options.Success)
break;
}
catch
{
//log
}
retries++;
Thread.Sleep(options.BackOffInterval);
} while (retries < options.RetryAttempts);
}
}
public interface IActionRetryOptions
{
Action Action { get; set; }
uint RetryAttempts { get; set; }
TimeSpan BackOffInterval { get; set; }
}
public interface IActionRetryOptions<TResult>
{
TResult Result { get; set; }
Func<TResult> Action { get; set; }
uint RetryAttempts { get; set; }
bool Success { get; }
Func<TResult, bool> RetryAction { get; set; }
TimeSpan BackOffInterval { get; set; }
TResult InvokeAction();
}
public interface IActionRetryOptions<TIn, TResult>
{
TIn Args { get; set; }
TResult Result { get; set; }
Func<TIn, TResult> Action { get; set; }
uint RetryAttempts { get; set; }
bool Success { get; }
Func<TResult, bool> RetryAction { get; set; }
TimeSpan BackOffInterval { get; set; }
TResult InvokeAction();
}
public class ActionRetryOptions: IActionRetryOptions
{
public Action Action { get; set; }
public uint RetryAttempts { get; set; }
public TimeSpan BackOffInterval { get; set; } = TimeSpan.FromSeconds(3);
}
public class ActionRetryOptions<TResult>: IActionRetryOptions<TResult>
{
public TResult Result { get; set; }
public Func<TResult> Action { get; set; }
public uint RetryAttempts { get; set; }
public bool Success
{
get
{
return EvaluateResult();
}
}
public Func<TResult, bool> RetryAction { get; set; }
public TimeSpan BackOffInterval { get; set; } = TimeSpan.FromSeconds(3);
public TResult InvokeAction()
{
return Action.Invoke();
}
bool EvaluateResult()
{
if (Result == null)
throw new InvalidOperationException("Cannot evaluate retry without the action result. Invoke the Action to determine if successfull.");
else
return RetryAction.Invoke(this.Result);
}
}
public class ActionRetryOptions<TIn, TResult>: IActionRetryOptions<TIn, TResult>
{
public TIn Args { get; set; }
public TResult Result { get; set; }
public Func<TIn, TResult> Action { get; set; }
public uint RetryAttempts { get; set; }
public TResult InvokeAction()
{
return Action.Invoke(Args);
}
public bool Success
{
get
{
return EvaluateResult();
}
}
public Func<TResult, bool> RetryAction { get; set; }
public TimeSpan BackOffInterval { get; set; } = TimeSpan.FromSeconds(3);
bool EvaluateResult()
{
if (Result == null)
throw new InvalidOperationException("Cannot evaluate retry without the action result. Invoke the Action to determine if successfull.");
else
return RetryAction.Invoke(this.Result);
}
}
Issues:
- How do we support functions with at least one generic parameters and a generic output?
- What can we do better?
ActionRetryOptions<TIn, TResult>
with multipleTIn
s and change the properties to match. You just need to support as manyFunc
s as the BCL provides. \$\endgroup\$ActionRetryOptions
classes really feel like they should descend from an interface, and the builder pattern might come in helpful here, and is the non-caching the result ofEvaluateResult
inSuccess
intentional? \$\endgroup\$EvaluateResult
i dint see a need to cache it, but your feedback will be important. Why do you think we should cache it? 3. I will definitely include support for multipleTIn
. Thanks :) \$\endgroup\$Success
. This is such that you can store all types of option classes in a container structure. \$\endgroup\$Success
a field-backed property whose value is set byEvaluateResult
- I would not expect that enquiring about the success status of an action would cause it to fire. Make firing the action explicit by callingEvaluateResult
and then accessingSuccess
. \$\endgroup\$