Timeouts and cooperative cancellation in C#: linked CancellationTokenSource, CancelAfter, WaitAsync

C# Async & Multithreading November 13, 2026 csharp

Put a using on the CancellationTokenSource, because it holds a timer.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ======================================================================
// Q: How do you implement timeouts and cooperative cancellation correctly?
//
// - Put a using on the CancellationTokenSource, because it holds a timer.
// - await task.WaitAsync(timeout, ct) stops the waiting, not the operation,
//   which keeps running. Use it only when the API doesn't accept a token.
// - An OperationCanceledException because the client closed the connection is
//   not an error. Don't log it as a 500.
// - For HTTP calls: put the timeout in the resilience pipeline (Polly, Q11), not
//   by hand everywhere.
// ======================================================================

// Timeout for a single call that also respects request cancellation
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
    return await erp.GetWarehouseStockAsync(sku, cts.Token);
}
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
{
    throw new TimeoutException($"ERP stock lookup for {sku} timed out");   // timeout, not cancellation by the client
}

// CPU loop: cancellation is cooperative, you have to check it yourself
foreach (var row in rows)
{
    ct.ThrowIfCancellationRequested();
    Process(row);
}