A utility class that executes both synchronous and asynchronous actions with retry logic, using exponential backoff and jitter.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public static class RetryPolicy
{
private static readonly Random _random = new Random();
/// <summary>
/// Executes a synchronous action with retry logic, using exponential backoff and jitter.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="retryCount">The number of retry attempts (default is 3).</param>
/// <param name="baseDelayMs">The base delay in milliseconds (default is 500).</param>
/// <example>
/// <![CDATA[
/// void UnreliableOperation()
/// {
/// if (new Random().Next(2) == 0)
/// throw new Exception("Random failure!");
///
/// Console.WriteLine("Operation succeeded.");
/// }
///
/// RetryPolicy.Execute(UnreliableOperation, retryCount: 5, baseDelayMs: 200);
/// ]]>
/// </example>
public static void Execute(Action action, int retryCount = 3, int baseDelayMs = 500)
{
for (int attempt = 0; attempt < retryCount; attempt++)
{
try
{
action();
return;
}
catch
{
if (attempt == retryCount - 1) throw;
int maxDelay = baseDelayMs * (int)Math.Pow(2, attempt);
int jitter = _random.Next(0, maxDelay + 1);
Thread.Sleep(jitter);
}
}
}
/// <summary>
/// Executes a synchronous function with retry logic and returns a result, using exponential backoff and jitter.
/// </summary>
/// <typeparam name="T">Return type of the function.</typeparam>
/// <param name="func">The function to execute.</param>
/// <param name="retryCount">The number of retry attempts (default is 3).</param>
/// <param name="baseDelayMs">The base delay in milliseconds (default is 500).</param>
/// <returns>The result of the function.</returns>
/// <example>
/// <![CDATA[
/// int SometimesFails()
/// {
/// if (new Random().Next(3) != 0)
/// throw new Exception("Try again!");
///
/// return 42;
/// }
///
/// int result = RetryPolicy.Execute(SometimesFails);
/// Console.WriteLine(result);
/// ]]>
/// </example>
public static T Execute<T>(Func<T> func, int retryCount = 3, int baseDelayMs = 500)
{
for (int attempt = 0; attempt < retryCount; attempt++)
{
try
{
return func();
}
catch
{
if (attempt == retryCount - 1) throw;
int maxDelay = baseDelayMs * (int)Math.Pow(2, attempt);
int jitter = _random.Next(0, maxDelay + 1);
Thread.Sleep(jitter);
}
}
throw new Exception("Retry failed.");
}
/// <summary>
/// Executes an asynchronous action with retry logic, using exponential backoff and jitter.
/// </summary>
/// <param name="action">The asynchronous action to execute.</param>
/// <param name="retryCount">The number of retry attempts (default is 3).</param>
/// <param name="baseDelayMs">The base delay in milliseconds (default is 500).</param>
/// <example>
/// <![CDATA[
/// async Task SometimesFailsAsync()
/// {
/// if (new Random().Next(2) == 0)
/// throw new Exception("Boom!");
///
/// Console.WriteLine("Async success!");
/// }
///
/// await RetryPolicy.ExecuteAsync(() => SometimesFailsAsync(), retryCount: 4, baseDelayMs: 300);
/// ]]>
/// </example>
public static async Task ExecuteAsync(Func<Task> action, int retryCount = 3, int baseDelayMs = 500)
{
for (int attempt = 0; attempt < retryCount; attempt++)
{
try
{
await action();
return;
}
catch
{
if (attempt == retryCount - 1) throw;
int maxDelay = baseDelayMs * (int)Math.Pow(2, attempt);
int jitter = _random.Next(0, maxDelay + 1);
await Task.Delay(jitter);
}
}
}
/// <summary>
/// Executes an asynchronous function with retry logic and returns a result, using exponential backoff and jitter.
/// </summary>
/// <typeparam name="T">Return type of the asynchronous function.</typeparam>
/// <param name="func">The asynchronous function to execute.</param>
/// <param name="retryCount">The number of retry attempts (default is 3).</param>
/// <param name="baseDelayMs">The base delay in milliseconds (default is 500).</param>
/// <returns>The result of the asynchronous function.</returns>
/// <example>
/// <![CDATA[
/// async Task<int> GetNumberAsync()
/// {
/// if (new Random().Next(3) != 0)
/// throw new Exception("No luck!");
///
/// return 123;
/// }
///
/// int number = await RetryPolicy.ExecuteAsync(() => GetNumberAsync(), retryCount: 5);
/// Console.WriteLine(number);
/// ]]>
/// </example>
public static async Task<T> ExecuteAsync<T>(Func<Task<T>> func, int retryCount = 3, int baseDelayMs = 500)
{
for (int attempt = 0; attempt < retryCount; attempt++)
{
try
{
return await func();
}
catch
{
if (attempt == retryCount - 1) throw;
int maxDelay = baseDelayMs * (int)Math.Pow(2, attempt);
int jitter = _random.Next(0, maxDelay + 1);
await Task.Delay(jitter);
}
}
throw new Exception("Retry failed.");
}
}