The timeout proactive resilience strategy cancels the execution if it does not complete within the specified timeout period. If the execution is canceled by the timeout strategy, it throws a TimeoutRejectedException.
It is crucial that your callback respects the cancellation token. If it does not, the callback will continue executing even after cancellation, thereby ignoring the timeout.
// Simple timeout of 3 secondsvar pipeline = new ResiliencePipelineBuilder() .AddTimeout(TimeSpan.FromSeconds(3)) .Build();try{ await pipeline.ExecuteAsync(async ct => { // Your operation that should complete within 3 seconds await CallExternalServiceAsync(ct); }, cancellationToken);}catch (TimeoutRejectedException){ // Operation took too long Console.WriteLine("Operation timed out");}
var options = new TimeoutStrategyOptions{ TimeoutGenerator = static args => { // Adjust timeout based on context var isHighPriority = args.Context.Properties .TryGetValue(new ResiliencePropertyKey<bool>("IsHighPriority"), out var priority) && priority; var timeout = isHighPriority ? TimeSpan.FromSeconds(60) : TimeSpan.FromSeconds(10); return new ValueTask<TimeSpan>(timeout); }};
The timeout strategy relies on co-operative cancellation. Your callbacks must honor the cancellation token:
var pipeline = new ResiliencePipelineBuilder() .AddTimeout(TimeSpan.FromSeconds(1)) .Build();await pipeline.ExecuteAsync( static async innerToken => { // ✅ Use the innerToken provided by the pipeline await Task.Delay(TimeSpan.FromSeconds(3), innerToken); }, outerToken);
If the cancellation token is not respected, the callback will continue executing after the timeout, and the strategy will have to wait for it to complete before throwing TimeoutRejectedException.
var pipeline = new ResiliencePipelineBuilder<List<Customer>>() .AddTimeout(TimeSpan.FromSeconds(5)) .Build();var customers = await pipeline.ExecuteAsync(async ct =>{ await using var connection = new SqlConnection(connectionString); await connection.OpenAsync(ct); await using var command = new SqlCommand("SELECT * FROM Customers", connection); // Ensure the command respects the cancellation token ct.Register(() => command.Cancel()); var results = new List<Customer>(); await using var reader = await command.ExecuteReaderAsync(ct); while (await reader.ReadAsync(ct)) { results.Add(new Customer { Id = reader.GetInt32(0), Name = reader.GetString(1) }); } return results;}, cancellationToken);
var operationTimeoutKey = new ResiliencePropertyKey<TimeSpan>("OperationTimeout");var options = new TimeoutStrategyOptions{ TimeoutGenerator = args => { // Get timeout from context or use default var timeout = args.Context.Properties.GetValue( operationTimeoutKey, TimeSpan.FromSeconds(30)); return new ValueTask<TimeSpan>(timeout); }};var pipeline = new ResiliencePipelineBuilder().AddTimeout(options).Build();// Use with custom timeoutvar context = ResilienceContextPool.Shared.Get();context.Properties.Set(operationTimeoutKey, TimeSpan.FromSeconds(5));try{ await pipeline.ExecuteAsync( async (ctx, ct) => await SlowOperationAsync(ct), context, cancellationToken);}finally{ ResilienceContextPool.Shared.Return(context);}
// Each retry attempt has its own 5-second timeout// Total time could be up to (3 attempts × 5 seconds) + retry delaysvar pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>() .AddRetry(new RetryStrategyOptions<HttpResponseMessage> { MaxRetryAttempts = 3, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder<HttpResponseMessage>() .Handle<TimeoutRejectedException>() .Handle<HttpRequestException>() }) .AddTimeout(TimeSpan.FromSeconds(5)) .Build();var response = await pipeline.ExecuteAsync(async ct =>{ return await httpClient.GetAsync(url, ct);}, cancellationToken);
OnTimeout is called before the exception is thrown. This is useful when combining with retry strategy, as the retry might handle the exception. If you use try-catch, you might not catch the exception if retry succeeds on a subsequent attempt.
What happens if my operation ignores the cancellation token?
The timeout strategy will wait for your operation to complete before throwing TimeoutRejectedException. This defeats the purpose of the timeout. Always respect the cancellation token in your callbacks.
Can I use different timeouts for different operations?
Yes! Use TimeoutGenerator to dynamically calculate timeouts based on context properties, operation key, or any other runtime information.
Does timeout affect the original cancellation token?
The timeout strategy wraps the incoming cancellation token with a new one. If the original token is canceled, the timeout strategy honors it without throwing TimeoutRejectedException.
Should timeout be inside or outside retry?
It depends:
Inside retry (retry → timeout): Each attempt gets its own timeout
Outside retry (timeout → retry): The entire retry sequence must complete within the timeout