Lesson 17 / 25
Concurrency with Tasks and Cancellation
Run tasks concurrently, cancel them, and stream results asynchronously.
Coordinating many operations
Task.WhenAll awaits several tasks started together and returns all results, so three independent 1-second calls take about 1 second rather than 3. Task.WhenAny completes when the first task finishes, useful for timeouts or racing replicas. Cancellation is cooperative: pass a CancellationToken down through every async method, and libraries such as HttpClient and EF Core stop work when it is cancelled, throwing OperationCanceledException. Create tokens with a CancellationTokenSource, for example new CancellationTokenSource(TimeSpan.FromSeconds(5)) for a timeout. IAsyncEnumerable<T> with await foreach streams results that arrive asynchronously, such as pages of an API. Parallel.ForEachAsync processes many items with a limit on concurrency. ValueTask<T> can avoid an allocation when results are often available synchronously, but must not be awaited twice.
WhenAll, a timeout and limited parallelism
The token flows into every call so a slow dependency cannot hang the request forever.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var ct = cts.Token;
// three independent calls run concurrently
Task<decimal> priceTask = GetPriceAsync("sku-1", ct);
Task<int> stockTask = GetStockAsync("sku-1", ct);
Task<double> ratingTask = GetRatingAsync("sku-1", ct);
await Task.WhenAll(priceTask, stockTask, ratingTask);
Console.WriteLine($"{priceTask.Result} / {stockTask.Result} / {ratingTask.Result}"); // safe: already completed
// process 1,000 images, at most 8 at a time
var files = Directory.GetFiles("uploads", "*.jpg");
await Parallel.ForEachAsync(files, new ParallelOptions { MaxDegreeOfParallelism = 8, CancellationToken = ct },
async (file, token) => await ResizeAsync(file, token));
// streaming results
await foreach (var page in GetPagesAsync(ct))
Console.WriteLine(page.Count);Ordering at a food court
Awaiting one stall at a time is queueing at each counter in turn. Task.WhenAll is placing orders at three stalls, then collecting all three trays when the buzzers go off.
Quick check: How should a long-running async operation be stopped when the user cancels a request?
- Call Thread.Abort
- Pass a CancellationToken through the call chain and let operations observe it
- Use .Result to block it
- Throw an exception from another thread
Answer
Pass a CancellationToken through the call chain and let operations observe it — Cancellation in .NET is cooperative via CancellationToken.