Section 4 of 6
Async Best Practices
🎯 What You'll Learn
- Why use async/await
- Common mistakes
- ConfigureAwait
- ValueTask
- Async all the way
Why Use Async/Await?
Async/await improves scalability by freeing threads during I/O operations (database, API calls, file access), allowing them to handle other requests.
✅ Do's
1. Use Async All the Way
Async All the Way
C#
// ✅ Good
public async Task<IActionResult> Index()
{
var products = await _context.Products.ToListAsync();
return View(products);
}
2. Return Task Directly When Possible
Return Task
C#
// ✅ Good - no async/await needed
public Task<Product> GetProductAsync(int id)
{
return _context.Products.FindAsync(id).AsTask();
}
❌ Don'ts
1. Don't Block on Async Code
Blocking (DON'T DO THIS!)
C#
// ❌ Bad - causes deadlocks!
var products = _context.Products.ToListAsync().Result; // DON'T!
var product = _context.Products.FindAsync(1).Wait(); // DON'T!
// ✅ Good
var products = await _context.Products.ToListAsync();
2. Don't Use async void
async void (DON'T DO THIS!)
C#
// ❌ Bad - can't catch exceptions!
public async void ProcessOrder() { }
// ✅ Good
public async Task ProcessOrder() { }
3. Don't Mix Sync and Async
Mixing (DON'T DO THIS!)
C#
// ❌ Bad
public List<Product> GetProducts()
{
return _context.Products.ToListAsync().Result; // Deadlock risk!
}
// ✅ Good
public async Task<List<Product>> GetProductsAsync()
{
return await _context.Products.ToListAsync();
}
ConfigureAwait
In ASP.NET Core, you don't need ConfigureAwait(false) - there's no SynchronizationContext.
ConfigureAwait
C#
// ASP.NET Core - ConfigureAwait(false) not needed
var products = await _context.Products.ToListAsync();
// Library code - use ConfigureAwait(false)
var products = await _context.Products.ToListAsync().ConfigureAwait(false);
ValueTask
Use ValueTask for hot paths that often complete synchronously.
ValueTask
C#
public async ValueTask<Product> GetProductAsync(int id)
{
// Check cache first (often returns synchronously)
if (_cache.TryGetValue(id, out Product product))
return product;
// Fetch from database
product = await _context.Products.FindAsync(id);
_cache.Set(id, product);
return product;
}
InvenTrack Example
ProductsController.cs
C#
public class ProductsController : Controller
{
// ✅ Good - async all the way
[HttpGet]
public async Task<IActionResult> Index()
{
var products = await _context.Products
.Include(p => p.Category)
.ToListAsync();
return View(products);
}
// ✅ Good - return Task directly
[HttpGet("{id}")]
public Task<IActionResult> Details(int id)
{
return GetProductDetailsAsync(id);
}
private async Task<IActionResult> GetProductDetailsAsync(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
return NotFound();
return View(product);
}
}
Key Takeaways
- Async all the way: Use async from top to bottom
- Never block: Don't use .Result or .Wait()
- async Task: Not async void (except event handlers)
- Return Task: When no await needed
- ConfigureAwait: Not needed in ASP.NET Core
- ValueTask: For hot paths with caching