Lesson 17 of 40
C# Language
Advanced
50 min
Async Programming Mastery
Master async/await, ValueTask, IAsyncEnumerable, channels, and avoid the common pitfalls that cause deadlocks and performance issues.
Part 1: ValueTask vs Task
Use
ValueTask when a method frequently completes synchronously (e.g., cache hits):public ValueTask<User> GetFromCacheAsync(int id)
{
if (_cache.TryGetValue(id, out var user))
return ValueTask.FromResult(user); // No allocation!
return new ValueTask<User>(FetchFromDbAsync(id));
}
{
if (_cache.TryGetValue(id, out var user))
return ValueTask.FromResult(user); // No allocation!
return new ValueTask<User>(FetchFromDbAsync(id));
}
Part 2: IAsyncEnumerable Streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var order in _db.Orders.AsAsyncEnumerable().WithCancellation(ct))
yield return order;
}
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var order in _db.Orders.AsAsyncEnumerable().WithCancellation(ct))
yield return order;
}
Part 3: Channels for Producer-Consumer
var channel = Channel.CreateBounded<Work>(capacity: 100);
// Producer
await channel.Writer.WriteAsync(new Work(id));
// Consumer
await foreach (var work in channel.Reader.ReadAllAsync())
await ProcessAsync(work);
// Producer
await channel.Writer.WriteAsync(new Work(id));
// Consumer
await foreach (var work in channel.Reader.ReadAllAsync())
await ProcessAsync(work);
Part 4: Common Async Pitfalls
| Anti-Pattern | Fix |
|---|---|
| .Result / .Wait() | Use await all the way up |
| async void | Use async Task (except event handlers) |
| Missing ConfigureAwait | Add ConfigureAwait(false) in libraries |
| Fire-and-forget without error handling | Wrap in try/catch or use IHostedService |