.NET Asynchronous Programming
A guide for APIs and patterns for efficient asynchronous programming.
Quick Reference: See QUICKREF.md for essential patterns at a glance.
- Core APIs
API Purpose
Task
Async operation (no return value)
Task<T>
Async operation (with return value)
ValueTask<T>
Optimization for high-frequency calls
IAsyncEnumerable<T>
Async streams
- Task vs ValueTask
2.1 When to Use Task
-
Most async operations
-
When actual async work always occurs
2.2 When to Use ValueTask
-
When synchronous completion is frequent (cache hits)
-
High-frequency call methods
// Use ValueTask when cache hits are frequent public ValueTask<Data> GetDataAsync(string key) { if (_cache.TryGetValue(key, out var cached)) { // Synchronous return (no Heap allocation) return new ValueTask<Data>(cached); }
// When async work is needed
return new ValueTask<Data>(LoadFromDbAsync(key));
}
2.3 ValueTask Cautions
// ❌ Bad example: Awaiting ValueTask multiple times var task = GetDataAsync("key"); var result1 = await task; var result2 = await task; // May cause error!
// ✅ Good example: Await only once var result = await GetDataAsync("key");
- ConfigureAwait
// Use ConfigureAwait(false) in libraries public async Task<string> FetchDataAsync() { var response = await _httpClient.GetAsync(url) .ConfigureAwait(false); return await response.Content.ReadAsStringAsync() .ConfigureAwait(false); }
- Async Streams (IAsyncEnumerable)
public async IAsyncEnumerable<Data> GetDataStreamAsync( [EnumeratorCancellation] CancellationToken ct = default) { await foreach (var item in _source.ReadAllAsync(ct)) { yield return await ProcessAsync(item); } }
// Consuming await foreach (var data in GetDataStreamAsync(ct)) { Console.WriteLine(data); }
- Cancellation Token
public async Task<Data> LoadDataAsync(CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); return await _httpClient.GetFromJsonAsync<Data>(url, ct); }
// Setting timeout using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); await LongRunningOperationAsync(cts.Token);
- Concurrency Control
private readonly SemaphoreSlim _semaphore = new(maxCount: 10);
public async Task ProcessWithThrottlingAsync(Data data) { await _semaphore.WaitAsync(); try { await ProcessAsync(data); } finally { _semaphore.Release(); } }
- Anti-patterns
// ❌ No async void (cannot handle exceptions) public async void BadMethod() { }
// ✅ async Task public async Task GoodMethod() { }
// ❌ No .Result, .Wait() (deadlock risk) var result = GetDataAsync().Result;
// ✅ Use await var result = await GetDataAsync();
// ❌ Unnecessary async/await public async Task<Data> GetDataAsync() { return await _repository.GetAsync(); }
// ✅ Direct return public Task<Data> GetDataAsync() { return _repository.GetAsync(); }
- References
-
Task-based Asynchronous Pattern
-
ValueTask