Async & concurrency cheat sheet
Tasks, await, WhenAll, cancellation, channels and the pitfalls to avoid.
Basics
async Task<string> LoadAsync(string url, CancellationToken ct = default)
{
using var http = new HttpClient();
return await http.GetStringAsync(url, ct); // waits without blocking the thread
}
async Task SaveAsync() { await db.SaveChangesAsync(); } // no result β Task
string html = await LoadAsync("https://example.com"); // top-level await works| Return type | Use for |
|---|---|
Task | No result |
Task<T> | A result of type T |
ValueTask<T> | Hot paths that usually complete synchronously |
IAsyncEnumerable<T> | Async streams β await foreach |
async void | Event handlers only |
Concurrency
// Sequential β one after another
var a = await GetAAsync();
var b = await GetBAsync();
// Concurrent β start first, then await
Task<A> ta = GetAAsync();
Task<B> tb = GetBAsync();
await Task.WhenAll(ta, tb);
string[] pages = await Task.WhenAll(urls.Select(u => http.GetStringAsync(u)));
Task first = await Task.WhenAny(t1, t2); // first to finish
await Task.Delay(500); // non-blocking wait
var result = await Task.Run(() => HeavyCpuWork()); // CPU-bound work onlyCancellation and timeouts
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // timeout
cts.Cancel(); // manual
await DoWorkAsync(cts.Token);
token.ThrowIfCancellationRequested(); // in loops
try { await op; } catch (OperationCanceledException) { /* cancelled or timed out */ }
using var linked = CancellationTokenSource.CreateLinkedTokenSource(requestToken);
linked.CancelAfter(TimeSpan.FromSeconds(10));
await task.WaitAsync(TimeSpan.FromSeconds(5)); // TimeoutExceptionThread safety
Interlocked.Increment(ref counter);
lock (gate) { /* short critical section β no await inside */ }
await semaphore.WaitAsync(); try { ... } finally { semaphore.Release(); } // async lock
var cache = new ConcurrentDictionary<string, Product>(); cache.GetOrAdd(key, Load);Channels (producer / consumer)
var channel = Channel.CreateBounded<Job>(100); // back-pressure when full
await channel.Writer.WriteAsync(job); channel.Writer.Complete();
await foreach (var j in channel.Reader.ReadAllAsync(ct)) await ProcessAsync(j);Parallel CPU work
Parallel.ForEach(items, item => Process(item));
await Parallel.ForEachAsync(urls, new ParallelOptions { MaxDegreeOfParallelism = 4 },
async (url, ct) => await DownloadAsync(url, ct));Pitfalls
| β Don't | β Do |
|---|---|
task.Result, task.Wait(), GetAwaiter().GetResult() | await task β async all the way |
async void DoWork() | async Task DoWorkAsync() |
| Call an async method without awaiting it | Await it, or queue real background work |
Task.Run(() => db.Query()) for I/O | Use the async API: await db.QueryAsync() |
Thread.Sleep(1000) | await Task.Delay(1000) |
lock around await | SemaphoreSlim.WaitAsync() |
Ignore CancellationToken parameters | Pass them down to every async call |