Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/App-vNext/Polly/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Proactive strategies make decisions to cancel or reject callback execution without focusing on individual results. This guide demonstrates how to create a Timing Strategy that tracks execution times and reports when thresholds are exceeded.
Proactive strategies inherit from ResilienceStrategy (non-generic) and work across various result types.

Implementation

Strategy Class

Proactive strategies derive from the non-generic ResilienceStrategy base class:
// Strategies should be internal and not exposed in the library's public API.
// Configure the strategy through extension methods and options.
internal sealed class TimingResilienceStrategy : ResilienceStrategy
{
    private readonly TimeSpan _threshold;
    private readonly Func<OnThresholdExceededArguments, ValueTask>? _onThresholdExceeded;
    private readonly ResilienceStrategyTelemetry _telemetry;

    public TimingResilienceStrategy(
        TimeSpan threshold,
        Func<OnThresholdExceededArguments, ValueTask>? onThresholdExceeded,
        ResilienceStrategyTelemetry telemetry)
    {
        _threshold = threshold;
        _telemetry = telemetry;
        _onThresholdExceeded = onThresholdExceeded;
    }

    protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
        Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
        ResilienceContext context,
        TState state)
    {
        var stopwatch = Stopwatch.StartNew();

        // Execute the given callback and adhere to the ContinueOnCapturedContext property value.
        Outcome<TResult> outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);

        if (stopwatch.Elapsed > _threshold)
        {
            // Bundle information about the event into arguments.
            var args = new OnThresholdExceededArguments(context, _threshold, stopwatch.Elapsed);

            // Report this as a resilience event if the execution took longer than the threshold.
            _telemetry.Report(
                new ResilienceEvent(ResilienceEventSeverity.Warning, "ExecutionThresholdExceeded"),
                context,
                args);

            if (_onThresholdExceeded is not null)
            {
                await _onThresholdExceeded(args).ConfigureAwait(context.ContinueOnCapturedContext);
            }
        }

        // Return the outcome directly.
        return outcome;
    }
}
Proactive strategies measure or control execution behavior rather than handling specific result types.

Event Arguments

Define arguments to encapsulate event details:
// Structs for arguments encapsulate details about specific events within the resilience strategy.
// Relevant properties to the event can be exposed. In this event, the actual execution time and the exceeded threshold are included.
public readonly struct OnThresholdExceededArguments
{
    public OnThresholdExceededArguments(ResilienceContext context, TimeSpan threshold, TimeSpan duration)
    {
        Context = context;
        Threshold = threshold;
        Duration = duration;
    }

    public TimeSpan Threshold { get; }

    public TimeSpan Duration { get; }

    // As per convention, all arguments should provide a "Context" property.
    public ResilienceContext Context { get; }
}
Arguments should always have an Arguments suffix and include a Context property. This design makes the API more extensible and maintainable.

Options

1

Define Options Class

Create a public options class that inherits from ResilienceStrategyOptions:
public class TimingStrategyOptions : ResilienceStrategyOptions
{
    public TimingStrategyOptions()
    {
        // Assign a default name to the options for more detailed telemetry insights.
        Name = "Timing";
    }

    // Apply validation attributes to guarantee the options' validity.
    // The pipeline will handle validation automatically during its construction.
    [Range(typeof(TimeSpan), "00:00:00", "1.00:00:00")]
    [Required]
    public TimeSpan? Threshold { get; set; }

    // Provide the delegate to be called when the threshold is surpassed.
    // Ideally, arguments should share the delegate's name, but with an "Arguments" suffix.
    public Func<OnThresholdExceededArguments, ValueTask>? OnThresholdExceeded { get; set; }
}
2

Use Validation Attributes

Apply data annotation attributes to validate options:
  • [Required] for mandatory properties
  • [Range] for value constraints
  • Pipeline automatically validates during construction
Options represent the public contract with consumers. Use validation attributes to ensure correctness.

Extension Methods

Proactive strategies can use a single extension method that works for both generic and non-generic builders:
public static class TimingResilienceStrategyBuilderExtensions
{
    // The extensions should return the builder to support a fluent API.
    // For proactive strategies, we can target both "ResiliencePipelineBuilderBase" and "ResiliencePipelineBuilder<T>"
    // using generic constraints.
    public static TBuilder AddTiming<TBuilder>(this TBuilder builder, TimingStrategyOptions options)
        where TBuilder : ResiliencePipelineBuilderBase
    {
        // Add the strategy through the AddStrategy method. This method accepts a factory delegate
        // and automatically validates the options.
        return builder.AddStrategy(
            context =>
            {
                // The "context" provides various properties for the strategy's use.
                // In this case, we simply use the "Telemetry" property and pass it to the strategy.
                // The Threshold and OnThresholdExceeded values are sourced from the options.
                var strategy = new TimingResilienceStrategy(
                    options.Threshold!.Value,
                    options.OnThresholdExceeded,
                    context.Telemetry);

                return strategy;
            },
            options);
    }
}
By using generic constraints on ResiliencePipelineBuilderBase, a single extension method supports both ResiliencePipelineBuilder and ResiliencePipelineBuilder<T>.

Usage Example

// Add the proactive strategy to the builder
var pipeline = new ResiliencePipelineBuilder()
    // This is custom extension defined in this sample
    .AddTiming(new TimingStrategyOptions
    {
        Threshold = TimeSpan.FromSeconds(1),
        OnThresholdExceeded = args =>
        {
            Console.WriteLine("Execution threshold exceeded!");
            return default;
        },
    })
    .Build();

Complete Example

Here’s how the timing strategy works in a complete scenario:
1

Create Pipeline

Build a resilience pipeline with the timing strategy:
var pipeline = new ResiliencePipelineBuilder()
    .AddTiming(new TimingStrategyOptions
    {
        Threshold = TimeSpan.FromSeconds(1),
        OnThresholdExceeded = args =>
        {
            Console.WriteLine($"Execution took {args.Duration.TotalSeconds}s, "
                + $"exceeding threshold of {args.Threshold.TotalSeconds}s");
            return default;
        },
    })
    .Build();
2

Execute Operations

Use the pipeline to execute operations:
// Fast operation - no event triggered
await pipeline.ExecuteAsync(async ct =>
{
    await Task.Delay(500, ct);
    return "Fast operation";
});

// Slow operation - event triggered
await pipeline.ExecuteAsync(async ct =>
{
    await Task.Delay(1500, ct);
    return "Slow operation";
});
3

Monitor Results

The strategy automatically monitors execution time and triggers events when thresholds are exceeded.

Key Differences from Reactive Strategies

  • Proactive: Inherit from ResilienceStrategy (non-generic)
  • Reactive: Inherit from ResilienceStrategy<T> (generic)
  • Proactive: Arguments only need Context property
  • Reactive: Arguments need both Context and Outcome properties
  • Proactive: Monitor or control execution behavior
  • Reactive: Handle specific results or exceptions
  • Proactive: Single extension using ResiliencePipelineBuilderBase
  • Reactive: Separate extensions for generic and non-generic builders

Key Takeaways

Strategy Implementation

  • Inherit from non-generic ResilienceStrategy
  • Keep the strategy class internal
  • Measure or control execution behavior
  • Report events using ResilienceStrategyTelemetry

Arguments

  • Use readonly structs
  • Always include Context property
  • Add event-specific properties
  • Follow naming: {DelegateName}Arguments

Options

  • Inherit from ResilienceStrategyOptions
  • Use validation attributes
  • Provide sensible defaults
  • Set default strategy name

Extension Methods

  • Target ResiliencePipelineBuilderBase
  • Return builder for fluent API
  • Use AddStrategy for registration
  • Automatic options validation

Resources

Timing Strategy Sample

Complete working example from this guide

Timeout Strategy Source

Built-in timeout strategy implementation

Rate Limiter Source

Built-in rate limiter strategy implementation

Extensibility Overview

Learn about extensibility basics