Skip to content

Instantly share code, notes, and snippets.

@jabez007
Created April 4, 2022 21:21
Show Gist options
  • Save jabez007/69b6044eec14a2279fe3bda58dc21fd2 to your computer and use it in GitHub Desktop.
Save jabez007/69b6044eec14a2279fe3bda58dc21fd2 to your computer and use it in GitHub Desktop.
Implementing a Retry Pattern for Async Tasks in C#
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// Expanding on the idea from https://alastaircrabtree.com/implementing-the-retry-pattern-for-async-tasks-in-c/
/// </summary>
public static class RetryHelper
{
#region Static Delay
/// <summary>
///
/// </summary>
/// <param name="maxAttempts"></param>
/// <param name="delay"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync(int maxAttempts, TimeSpan delay, Func<Task> operation) =>
RetryOnExceptionAsync<Exception>(maxAttempts, delay, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delay"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult>(
int maxAttempts, TimeSpan delay, Func<Task<TResult>> operation
) =>
RetryOnExceptionAsync<TResult, Exception>(maxAttempts, delay, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delay"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync<TException>(
int maxAttempts, TimeSpan delay, Func<Task> operation
) where TException : Exception =>
RetryOnExceptionAsync<int, TException>(
maxAttempts,
delay,
async () =>
{
await operation();
return 0;
}
);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delay"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult, TException>(
int maxAttempts, TimeSpan delay, Func<Task<TResult>> operation
) where TException : Exception =>
RetryOnExceptionAsync<TResult, TException>(
maxAttempts,
(_) => CreateDelayForException(delay),
operation
);
#endregion
#region Auto Increasing Delay
/// <summary>
///
/// </summary>
/// <param name="maxAttempts"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync(int maxAttempts, Func<Task> operation) =>
RetryOnExceptionAsync<Exception>(maxAttempts, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult>(
int maxAttempts, Func<Task<TResult>> operation
) =>
RetryOnExceptionAsync<TResult, Exception>(maxAttempts, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync<TException>(
int maxAttempts, Func<Task> operation
) where TException : Exception =>
RetryOnExceptionAsync<int, TException>(
maxAttempts,
async () =>
{
await operation();
return 0;
}
);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult, TException>(
int maxAttempts, Func<Task<TResult>> operation
) where TException : Exception =>
RetryOnExceptionAsync<TResult, TException>(
maxAttempts,
(_) => CreateDelayForException(IncreasingDelayInSeconds(_, DelayPerAttempt)),
operation
);
#endregion
#region Different Delay per Attempt
/// <summary>
///
/// </summary>
/// <param name="maxAttempts"></param>
/// <param name="delayPerAttempt"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync(
int maxAttempts, TimeSpan[] delayPerAttempt, Func<Task> operation
) =>
RetryOnExceptionAsync<Exception>(maxAttempts, delayPerAttempt, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delayPerAttempt"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult>(
int maxAttempts, TimeSpan[] delayPerAttempt, Func<Task<TResult>> operation
) =>
RetryOnExceptionAsync<TResult, Exception>(maxAttempts, delayPerAttempt, operation);
/// <summary>
///
/// </summary>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delayPerAttempt"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task RetryOnExceptionAsync<TException>(
int maxAttempts, TimeSpan[] delayPerAttempt, Func<Task> operation
) where TException : Exception =>
RetryOnExceptionAsync<int, TException>(
maxAttempts,
delayPerAttempt,
async () =>
{
await operation();
return 0;
}
);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delayPerAttempt"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static Task<TResult> RetryOnExceptionAsync<TResult, TException>(
int maxAttempts, TimeSpan[] delayPerAttempt, Func<Task<TResult>> operation
) where TException : Exception =>
RetryOnExceptionAsync<TResult, TException>(
maxAttempts,
(_) => CreateDelayForException(IncreasingDelayInSeconds(_, delayPerAttempt)),
operation
);
#endregion
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <typeparam name="TException"></typeparam>
/// <param name="maxAttempts"></param>
/// <param name="delay"></param>
/// <param name="operation"></param>
/// <returns></returns>
public static async Task<TResult> RetryOnExceptionAsync<TResult, TException>(
int maxAttempts, Func<int, Task> delay, Func<Task<TResult>> operation) where TException : Exception
{
if (maxAttempts <= 0)
throw new ArgumentOutOfRangeException(nameof(maxAttempts));
var attempts = 0;
do
{
try
{
attempts++;
return await operation();
}
catch (TException)
{
if (attempts >= maxAttempts)
throw;
await delay(attempts);
}
} while (true);
}
private static Task CreateDelayForException(TimeSpan delay) =>
Task.Delay(delay);
private static TimeSpan[] DelayPerAttempt =
{
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30),
TimeSpan.FromMinutes(2),
TimeSpan.FromMinutes(10),
TimeSpan.FromMinutes(30)
};
private static TimeSpan IncreasingDelayInSeconds(int failedAttempts, TimeSpan[] delayPerAttempt)
{
if (failedAttempts <= 0) throw new ArgumentOutOfRangeException(nameof(failedAttempts));
return failedAttempts > delayPerAttempt.Length
? delayPerAttempt.Last()
: delayPerAttempt[failedAttempts];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment