\$\begingroup\$
\$\endgroup\$
5
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?
Vogel612
25.5k7 gold badges59 silver badges141 bronze badges
2 Answers 2
\$\begingroup\$
\$\endgroup\$
2
- You should have an exponential back-off policy i.e. 5 sec, 10 sec, 15 sec.
- Why do you need the interface for the options classes? You won't be needing multiple implementations of the options classes so you don't need the interface
- The catch all part
catch { }
is overly generic. You are catching all exceptions including those you shouldn't retry! i.e. imagine you calling a http endpoint and you get a 401/403 repeatedly calling the service won't help you out.- In evaluate result,
null
could be a valid return value. You need a boolean flag to know if it the function was invoked.
- In evaluate result,
-
1\$\begingroup\$ The interface is there so that a container can be made to hold multiple types of options classes; the OP misunderstood my comment, see clarifying comment below question. \$\endgroup\$Tamoghna Chowdhury– Tamoghna Chowdhury2017年08月19日 10:13:42 +00:00Commented Aug 19, 2017 at 10:13
-
1\$\begingroup\$ 5,10,15 seems pretty linear to me. .. exponential would be something like 1, 2, 4, 8 \$\endgroup\$Vogel612– Vogel6122017年08月20日 10:23:30 +00:00Commented Aug 20, 2017 at 10:23
\$\begingroup\$
\$\endgroup\$
0
I think a loop would be cleaner here
uint retries = 0;
do
{
try
{
options.Action();
break;
}
catch
{
//log
}
retries++;
Thread.Sleep(options.BackOffInterval);
} while (retries < options.RetryAttempts);
for (uint retries = 0; retries < options.RetryAttempts; retries++) { try { options.Action(); break; } catch { //log } Thread.Sleep(options.BackOffInterval); }
answered Aug 19, 2017 at 13:39
lang-cs
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\$