using System.Diagnostics;
using System.Threading.Tasks;
public static async Task Main()
var pipeline = new ResiliencePipelineBuilder()
ShouldHandle = args => PredicateResult.True(),
Delay = TimeSpan.FromSeconds(1),
await pipeline.ExecuteAsync(async ct => { await Task.Delay(100); }, CancellationToken.None);
public sealed class RetryReporterStrategy: ResilienceStrategy
private readonly ResiliencePropertyKey<int> _attemptCount;
public RetryReporterStrategy(ResiliencePropertyKey<int> attemptCount)
_attemptCount = attemptCount;
protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
Stopwatch watch = Stopwatch.StartNew();
var result = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
context.Properties.TryGetValue(_attemptCount, out var attempts);
if (result.Exception is not null)
Console.WriteLine($"The operation failed after {attempts+1} tries, in {watch.Elapsed.TotalSeconds} seconds");
Console.WriteLine($"The operation successeded after {attempts+1} tries, in {watch.Elapsed.TotalSeconds} seconds");
public static class RetryExtensions
public static ResiliencePipelineBuilder AddFancyRetry(this ResiliencePipelineBuilder builder, RetryStrategyOptions options)
ResiliencePropertyKey<int> attemptCountKey = new(nameof(attemptCountKey));
.AddStrategy(context => new RetryReporterStrategy(attemptCountKey), options)
BackoffType = options.BackoffType,
DelayGenerator = options.DelayGenerator,
MaxDelay = options.MaxDelay,
MaxRetryAttempts = options.MaxRetryAttempts,
Randomizer = options.Randomizer,
args.Context.Properties.Set(attemptCountKey, args.AttemptNumber);
return options.OnRetry?.Invoke(args) ?? default;
ShouldHandle = options.ShouldHandle,
UseJitter = options.UseJitter