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