C# > Asynchronous Programming > Tasks and async/await > WhenAll and WhenAny
Using Task.WhenAll to Process Multiple Web Requests Concurrently
This snippet demonstrates how to use Task.WhenAll
to execute multiple asynchronous operations concurrently and wait for all of them to complete before proceeding. It simulates making several web requests and processing their responses.
Code Example
This C# code demonstrates the usage of Task.WhenAll
for parallel execution of asynchronous web requests. The Main
method creates a list of URLs and uses FetchUrlContentAsync
to asynchronously fetch the content of each URL. Task.WhenAll
is then used to wait for all the fetch operations to complete, handling potential exceptions that might occur during the process. The results are then processed and displayed. Error handling is included to demonstrate how to gracefully handle exceptions when some of the tasks fail.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
public class WhenAllExample
{
public static async Task Main(string[] args)
{
// Define a list of URLs to fetch
List<string> urls = new List<string>()
{
"https://www.example.com",
"https://www.microsoft.com",
"https://www.google.com"
};
Console.WriteLine("Starting multiple web requests concurrently...");
// Create a list of tasks, each fetching a URL
List<Task<string>> tasks = new List<Task<string>>();
foreach (string url in urls)
{
tasks.Add(FetchUrlContentAsync(url));
}
// Use Task.WhenAll to wait for all tasks to complete
try
{
string[] contents = await Task.WhenAll(tasks);
Console.WriteLine("All web requests completed.\n");
// Process the content of each URL
for (int i = 0; i < urls.Count; i++)
{
Console.WriteLine($"Content from {urls[i]}:\n{contents[i].Substring(0, Math.Min(contents[i].Length, 100))}...\n"); // Display first 100 characters
}
}
catch (AggregateException ex)
{
Console.WriteLine("One or more web requests failed:");
foreach (var innerException in ex.InnerExceptions)
{
Console.WriteLine($" - {innerException.Message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
Console.WriteLine("Program finished.");
}
// Asynchronous method to fetch the content of a URL
static async Task<string> FetchUrlContentAsync(string url)
{
using (HttpClient client = new HttpClient())
{
Console.WriteLine($"Fetching content from {url}...");
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode(); // Throw exception for bad status codes
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error fetching {url}: {ex.Message}");
return "Error: Could not fetch content.";
}
}
}
}
Concepts Behind the Snippet
Task.WhenAll
takes an array (or IEnumerable
) of Task
objects and returns a single Task
that represents the completion of all the tasks in the array. It's useful when you need to perform multiple independent operations concurrently and want to wait for all of them to finish before proceeding. It returns a Task
that completes when all of its constituent tasks complete. If any of the input tasks faults, the returned task will also fault, aggregating all exceptions into an AggregateException
. If a cancellation token is provided, cancellation of the input tasks will also cause the returned task to complete with a faulted state.
Real-Life Use Case
Imagine a scenario where you need to fetch data from multiple external APIs to construct a dashboard or report. Using Task.WhenAll
allows you to initiate all the API requests concurrently, significantly reducing the overall time required to gather the necessary data compared to making sequential requests. Another example is processing multiple files from a directory. Each file processing operation can be represented as a Task, and Task.WhenAll
can be used to wait for all file processing operations to complete before moving on to the next stage.
Best Practices
Task.WhenAll
in a try-catch block to handle potential exceptions. Pay attention to the AggregateException that might contain multiple exceptions from individual tasks.Task.WhenAll
are already started. If you create tasks but don't start them (e.g., using new Task(...)
without calling Start()
), Task.WhenAll
will wait indefinitely. Use Task.Run
or async
methods to create and start tasks efficiently.async/await
. Consider using ConfigureAwait(false)
to avoid deadlocks, especially in library code.
Interview Tip
Be prepared to discuss the advantages and disadvantages of using Task.WhenAll
compared to other concurrency mechanisms like parallel loops. Explain how it improves performance by allowing tasks to run concurrently and how it simplifies error handling by aggregating exceptions from all tasks. Also, understand the difference between Task.WhenAll
and Task.WhenAny
.
When to Use Task.WhenAll
Use Task.WhenAll
when you have multiple independent asynchronous operations that need to be executed concurrently, and you cannot proceed until all of them have completed successfully (or you have handled their failures). It's particularly suitable for I/O-bound operations, such as network requests or file access, where the CPU is not heavily utilized.
Memory Footprint
The memory footprint of Task.WhenAll
depends on the number of tasks it is waiting for and the size of the data being processed by those tasks. Each task consumes memory for its state and any captured variables. The aggregated result (e.g., the array of results from the tasks) also consumes memory. Consider using streams and iterators if dealing with very large datasets to minimize memory usage.
Alternatives
Parallel.ForEach
or Parallel.For
may be a better choice, as they automatically manage thread pool resources.
Pros
AggregateException
, making it easier to handle failures.
Cons
ConfigureAwait(false)
in library code.
FAQ
-
What happens if one of the tasks passed to
Task.WhenAll
throws an exception?
If any of the tasks passed toTask.WhenAll
throws an exception, the resulting task will also fault with anAggregateException
containing the exception(s) thrown by the individual tasks. You need to handle this exception in a try-catch block. -
How is
Task.WhenAll
different fromTask.WaitAll
?
Task.WhenAll
is an asynchronous method that returns aTask
, allowing the calling thread to remain responsive while the tasks are executing.Task.WaitAll
, on the other hand, is a synchronous method that blocks the calling thread until all tasks are completed.Task.WhenAll
is generally preferred in asynchronous code.