If you've written more than a handful of async methods in C#, you've probably written this pattern without thinking twice:

var user = await GetUserAsync(userId);
var orders = await GetOrdersAsync(userId);
var invoices = await GetInvoicesAsync(userId);

It's clean, it's readable, it looks correct, and it's quietly making your application slower than it needs to be.

The Problem: Sequential Awaits Add Up

Each await here suspends the method until that specific call completes before moving to the next line. If each call takes 200ms, this method takes 600ms minimum, even though none of these three calls depends on the result of another.

You're not calling GetOrdersAsync with anything from GetUserAsync. You're not using orders to fetch invoices either. These are three completely independent I/O operations, three separate round trips to a database, an API, or another service, and you're forcing them to happen one after another for no reason other than habit.

The Fix: Task.WhenAll()

Instead of awaiting each call as it starts, start all the tasks first, then await them together:

var userTask = GetUserAsync(userId);
var ordersTask = GetOrdersAsync(userId);
var invoicesTask = GetInvoicesAsync(userId);

await Task.WhenAll(userTask, ordersTask, invoicesTask);

var user = userTask.Result;
var orders = ordersTask.Result;
var invoices = invoicesTask.Result;

The three calls are now dispatched almost simultaneously. Total latency drops from the sum of all three (600ms) to roughly the duration of the slowest one (200ms), a 3x improvement, without touching a single thread manually.

Why This Actually Reduces Latency (and Not Just CPU Work)

It's worth being precise about why this helps, because Task.WhenAll() isn't a threading trick, most of the gain here comes from I/O, not CPU parallelism.

When you await an I/O bound call (an HTTP request, a database query), the thread isn't blocked waiting, it's released back to the thread pool while the operation happens elsewhere (in the OS, the network stack, the database server). Starting three I/O operations "at once" means three requests are in flight simultaneously, each doing its own waiting independently. You're not doing three times the work in parallel , you're doing three times the waiting in parallel, which is exactly where the latency was coming from.

Exception Handling: The Part People Get Wrong

If multiple tasks fail, only the first exception is thrown from the awaited call, but all the tasks' exceptions are still captured.

try
{
    await Task.WhenAll(userTask, ordersTask, invoicesTask);
}
catch (Exception)
{
    // Only the first exception is rethrown here.
    // To inspect all failures:
    var errors = new[] { userTask, ordersTask, invoicesTask }
        .Where(t => t.IsFaulted)
        .SelectMany(t => t.Exception!.InnerExceptions);

    foreach (var ex in errors)
    {
        // log each one
    }
}