When two asynchronous operations do not depend on each other, start both before awaiting either one. That lets the I/O work overlap instead of turning the method into a sequence of unnecessary waits.

This shows up often in API handlers and application services. Maybe one call loads the current customer, another loads feature flags, and a third calls an external pricing service. If those operations are independent, awaiting them one by one makes total latency closer to the sum of all three calls.

Start the tasks first, then await them with Task.WhenAll. After that, read each task’s result. The code still has one clear synchronization point, but the independent I/O operations can run at the same time.

Task<Customer> customerTask =
    customerClient.GetCustomerAsync(customerId, cancellationToken);

Task<IReadOnlyList<Price>> pricingTask =
    pricingClient.GetPricesAsync(cancellationToken);

await Task.WhenAll(customerTask, pricingTask);

Customer customer = await customerTask;
IReadOnlyList<Price> pricing = await pricingTask;

Await the completed tasks instead of reading .Result. It keeps the call path async and avoids changing exception behavior just because you are collecting results.

Be careful with EF Core. A single DbContext instance does not support multiple parallel operations. If two database queries truly need to run concurrently, use separate DbContext instances, for example through IDbContextFactory, and make sure the database can handle the extra concurrency.

Do not use this pattern when the second operation needs data from the first, when the operations must happen in a specific order, or when parallel calls would overload a dependency. For HTTP fan-out, also think about connection limits, downstream rate limits, timeouts, and cancellation. Concurrency is useful when the boundary can handle it. It is not a free performance switch.