Skip to main content

Command Palette

Search for a command to run...

Task.WhenAll

Mastering Task.WhenAll in .NET: Run Tasks Concurrently Like a Pro

Published
•3 min read•View as Markdown
Task.WhenAll
M

Morteza Jangjoo, Senior .NET Backend Developer with 15+ years of experience in C#, ASP.NET Core, SQL Server, and Microservices. Skilled in building scalable, high-performance systems.

As .NET developers, we often deal with scenarios where multiple asynchronous operations need to run — fetching data from APIs, reading files, calling services, or performing database queries. A common question arises: Should we await tasks one by one, or can we run them all at once?

That’s where Task.WhenAll comes into play.


1. Running Tasks Sequentially (Traditional Way)

The simplest approach is to await each task one after another:

public async Task SequentialExampleAsync()
{
    var result1 = await GetDataFromApiAsync("https://api.service1.com/data");
    var result2 = await GetDataFromApiAsync("https://api.service2.com/data");
    var result3 = await GetDataFromApiAsync("https://api.service3.com/data");

    Console.WriteLine($"{result1}, {result2}, {result3}");
}

Pros

  • Simple and easy to read.

  • Errors are handled as they happen.

Cons

  • Each task starts only after the previous one finishes.

  • If each call takes 1 second, total time = 3 seconds.


2. Running Tasks Concurrently with Task.WhenAll

Instead of waiting sequentially, you can start tasks at the same time and then await all of them together:

public async Task ParallelExampleAsync()
{
    var task1 = GetDataFromApiAsync("https://api.service1.com/data");
    var task2 = GetDataFromApiAsync("https://api.service2.com/data");
    var task3 = GetDataFromApiAsync("https://api.service3.com/data");

    var results = await Task.WhenAll(task1, task2, task3);

    Console.WriteLine($"{results[0]}, {results[1]}, {results[2]}");
}

Pros

  • All tasks run concurrently.

  • If each call takes 1 second, total time ≈ 1 second.

  • Cleaner code than manually await-ing tasks one by one.

Cons

  • If one task fails, the entire WhenAll throws an exception (though you can handle it).

  • Less control over individual task completion timing.


3. Error Handling with Task.WhenAll

When using Task.WhenAll, if multiple tasks throw exceptions, they are aggregated into an AggregateException.

try
{
    var task1 = GetDataFromApiAsync("https://api.invalid.com");
    var task2 = GetDataFromApiAsync("https://api.service2.com/data");

    await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Use Task.WhenAll carefully if multiple failures might happen.


4. Performance Comparison

Let’s simulate a workload:

public async Task<string> GetDataFromApiAsync(string url)
{
    await Task.Delay(1000); // Simulate 1-second work
    return $"Data from {url}";
}
  • Sequential (await each): ~3 seconds.

  • Concurrent (Task.WhenAll): ~1 second.

Key takeaway: If tasks are independent and don’t rely on each other’s result, Task.WhenAll provides a huge performance boost.


5. When to Use Which?

  • Use sequential await when:

    • Tasks depend on the result of previous ones.

    • Simplicity matters more than speed.

  • Use Task.WhenAll when:

    • Tasks are independent.

    • Performance and efficiency are important.


Conclusion

Task.WhenAll is a powerful tool in .NET for running multiple asynchronous operations in parallel. Compared to sequential await, it can drastically reduce total execution time while keeping your code clean.

Next time you’re fetching data from several services, don’t line them up one by one — let them run together with Task.WhenAll.


🎁 Get source code from github

I’m Morteza Jangjoo and “Explaining things I wish someone had explained to me”