using System.Threading.Tasks;
public sealed class RetryWithExponentialBackoff
private readonly int maxBackoffMs, initialWaitTimeMs, minJitterMs, maxJitterMs;
public RetryWithExponentialBackoff(int maxBackoffMs = 64000, int initialWaitTimeMs = 200, int minJitterMs = 100, int maxJitterMs = 1000)
this.maxBackoffMs = maxBackoffMs;
this.initialWaitTimeMs = initialWaitTimeMs;
this.minJitterMs = minJitterMs;
this.maxJitterMs = maxJitterMs;
public async Task RunAsync(Func<CancellationToken, Task> func, Func<Exception, bool> validator, CancellationToken ct)
var backoff = new ExponentialBackoff(this.maxBackoffMs, this.initialWaitTimeMs, this.minJitterMs, this.maxJitterMs);
while (!(spent = backoff.IsSpent))
ct.ThrowIfCancellationRequested();
throw new Exception("Backoff spent");
struct ExponentialBackoff
private readonly int m_maxBackoffMs, m_initialWaitTimeMs, m_minJitterMs, m_maxJitterMs;
private int m_currentWaitTime;
public ExponentialBackoff(int maxBackoffMs = 64000, int initialWaitTimeMs = 200, int minJitterMs = 100, int maxJitterMs = 1000)
m_maxBackoffMs = maxBackoffMs;
m_initialWaitTimeMs = initialWaitTimeMs;
m_minJitterMs = minJitterMs;
m_maxJitterMs = maxJitterMs;
m_currentWaitTime = initialWaitTimeMs / 2;
public Task Delay(CancellationToken ct)
var waitTime = m_currentWaitTime * 2;
var jitter = rnd.Next(m_minJitterMs, m_maxJitterMs + 1);
m_currentWaitTime = waitTime;
return Task.Delay(waitTime + jitter, ct);
return m_currentWaitTime >= m_maxBackoffMs;
private static int i = 1;
public static string SimulateNetworkFail()
Console.WriteLine("SUCCESS!");
Console.WriteLine("Retrying...");
throw new Exception("ex");
public static void Main()
Console.WriteLine("Running test...");
var retry = new RetryWithExponentialBackoff(1000, 200, 100, 1000);
var task = retry.RunAsync(async (_) =>
}, (Exception error) => {
}, CancellationToken.None);