Put a using on the CancellationTokenSource, because it holds a timer.
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); } |