Asynchronous Programming Patterns
Master async/await and handle concurrent operations efficiently.
By EMEPublished: February 20, 2025
asyncawaitconcurrencymultithreadingperformanceasync-patterns
A Simple Analogy
Async programming is like a restaurant. Your waiter takes your order (starts async operation), then serves other tables instead of waiting. When food is ready, they bring it to you. You don't block waiting for your meal; other customers get served meanwhile.
What Is Asynchronous Programming?
Async programming allows operations to run without blocking the calling thread. While waiting for I/O (network, database), other work happens, improving responsiveness and throughput.
Why Use Async?
- Responsiveness: UI remains responsive during long operations
- Scalability: Threads freed to handle other requests
- Performance: Better resource utilization
- User experience: No frozen applications
Sync vs. Async
// Synchronous: Blocks for 5 seconds
public string FetchData()
{
Thread.Sleep(5000); // Blocking wait
return "Data";
}
// Asynchronous: Thread freed while waiting
public async Task<string> FetchDataAsync()
{
await Task.Delay(5000); // Non-blocking wait
return "Data";
}
Async/Await Pattern
// Define async method
public async Task<User> GetUserAsync(int id)
{
var response = await _httpClient.GetAsync($"/api/users/{id}");
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<User>(json);
}
// Call async method
var user = await GetUserAsync(123);
// Don't block on async code
var result = GetUserAsync(123).Result; // BAD: Deadlock risk!
Multiple Concurrent Operations
// Sequential: Slow (10 seconds total)
var user = await GetUserAsync(1);
var posts = await GetPostsAsync(user.Id);
var comments = await GetCommentsAsync(posts[0].Id);
// Concurrent: Fast (waits for slowest)
var userTask = GetUserAsync(1);
var postsTask = GetPostsAsync(1);
var commentsTask = GetCommentsAsync(1);
await Task.WhenAll(userTask, postsTask, commentsTask);
var (user, posts, comments) = (userTask.Result, postsTask.Result, commentsTask.Result);
Practical Example
public class DataService
{
private readonly HttpClient _http;
public async Task<OrderSummary> GetOrderDetailsAsync(int orderId)
{
// Run queries concurrently
var orderTask = _http.GetAsync($"/api/orders/{orderId}");
var itemsTask = _http.GetAsync($"/api/orders/{orderId}/items");
var shippingTask = _http.GetAsync($"/api/orders/{orderId}/shipping");
await Task.WhenAll(orderTask, itemsTask, shippingTask);
return new OrderSummary
{
Order = await DeserializeAsync(orderTask.Result),
Items = await DeserializeAsync(itemsTask.Result),
Shipping = await DeserializeAsync(shippingTask.Result)
};
}
}
Exception Handling
try
{
var data = await FetchAsync();
}
catch (HttpRequestException ex)
{
// Handle timeout or connection errors
Console.WriteLine($"Error: {ex.Message}");
}
// Exception in async operation propagates
public async Task SafeFetchAsync()
{
try
{
return await FetchAsync(); // Exceptions propagate
}
catch (Exception ex)
{
// Log and handle
throw;
}
}
Best Practices
- Use async all the way: Don't block on async code
- Avoid fire-and-forget: Track tasks, handle exceptions
- Use Task.WhenAll: Parallelize independent operations
- Avoid async void: Only for event handlers
- Handle exceptions: Always wrap in try-catch
Related Concepts to Explore
- Task-based asynchrony
- Concurrent collections
- Parallel processing
- CancellationToken for long-running operations
Summary
Async programming makes applications responsive and scalable. Master async/await, concurrent operations, and exception handling to build high-performance applications.