Task.WhenAll - Parallel Execution
Task.WhenAll runs multiple tasks concurrently and waits for all of them to complete. It's essential for maximizing throughput when tasks are independent.
Basic Usage
csharp1// Start multiple tasks2Task<string> task1 = FetchFromApi1Async();3Task<string> task2 = FetchFromApi2Async();4Task<string> task3 = FetchFromApi3Async();56// Wait for all to complete7await Task.WhenAll(task1, task2, task3);89// All tasks are now complete10string result1 = task1.Result; // Safe - already complete11string result2 = task2.Result;12string result3 = task3.Result;
Getting Results Directly
When all tasks return the same type, WhenAll returns an array:
csharp1Task<int>[] tasks = new[]2{3 ComputeAsync(1),4 ComputeAsync(2),5 ComputeAsync(3)6};78int[] results = await Task.WhenAll(tasks);9// results = [10, 20, 30]1011Console.WriteLine($"Sum: {results.Sum()}");
Sequential vs Parallel Comparison
csharp1// SEQUENTIAL - Takes 3 seconds2var sw = Stopwatch.StartNew();3var r1 = await FetchDataAsync("A"); // 1 second4var r2 = await FetchDataAsync("B"); // 1 second5var r3 = await FetchDataAsync("C"); // 1 second6Console.WriteLine($"Sequential: {sw.ElapsedMilliseconds}ms"); // ~3000ms78// PARALLEL - Takes 1 second9sw.Restart();10var t1 = FetchDataAsync("A"); // Start immediately11var t2 = FetchDataAsync("B"); // Start immediately12var t3 = FetchDataAsync("C"); // Start immediately13await Task.WhenAll(t1, t2, t3); // Wait for all14Console.WriteLine($"Parallel: {sw.ElapsedMilliseconds}ms"); // ~1000ms
Dynamic Number of Tasks
csharp1List<string> urls = GetUrlsToFetch();23// Create tasks dynamically4var tasks = urls.Select(url => httpClient.GetStringAsync(url));56// Wait for all7string[] results = await Task.WhenAll(tasks);89foreach (var result in results)10{11 ProcessResult(result);12}
Handling Exceptions
When multiple tasks fail, WhenAll aggregates exceptions:
csharp1var tasks = new[]2{3 FailingTaskAsync("Error 1"),4 SucceedingTaskAsync(),5 FailingTaskAsync("Error 2")6};78Task<int[]> allTask = Task.WhenAll(tasks);910try11{12 await allTask;13}14catch (Exception ex)15{16 // Only first exception thrown by await17 Console.WriteLine($"First error: {ex.Message}");1819 // Get ALL exceptions from the task20 if (allTask.Exception != null)21 {22 foreach (var inner in allTask.Exception.InnerExceptions)23 {24 Console.WriteLine($"Error: {inner.Message}");25 }26 }27}
Partial Success Pattern
Sometimes you want to process successful results even if some fail:
csharp1var tasks = urls.Select(async url =>2{3 try4 {5 return (Url: url, Data: await FetchAsync(url), Error: null as Exception);6 }7 catch (Exception ex)8 {9 return (Url: url, Data: null as string, Error: ex);10 }11});1213var results = await Task.WhenAll(tasks);1415var succeeded = results.Where(r => r.Error == null);16var failed = results.Where(r => r.Error != null);1718Console.WriteLine($"Succeeded: {succeeded.Count()}, Failed: {failed.Count()}");
WhenAll with Empty Collection
csharp1var emptyTasks = new List<Task<int>>();2int[] results = await Task.WhenAll(emptyTasks);3// results is an empty array, no exception
WhenAll with void Tasks
For tasks that don't return values:
csharp1Task[] tasks = new[]2{3 SendEmailAsync("user1@example.com"),4 SendEmailAsync("user2@example.com"),5 SendEmailAsync("user3@example.com")6};78await Task.WhenAll(tasks);9// All emails sent
Performance Considerations
Good: Independent Operations
csharp1// These are independent - good for WhenAll2var userData = FetchUserAsync(userId);3var orderData = FetchOrdersAsync(userId);4var preferences = FetchPreferencesAsync(userId);56await Task.WhenAll(userData, orderData, preferences);
Bad: Dependent Operations
csharp1// DON'T use WhenAll for dependent operations2var user = await GetUserAsync(id); // Need user first3var orders = await GetOrdersAsync(user.Id); // Depends on user4var details = await GetDetailsAsync(orders); // Depends on orders
Limiting Concurrency
WhenAll starts all tasks immediately. For rate limiting, see Topic 5 (Throttling).
csharp1// WARNING: This starts 1000 concurrent requests!2var tasks = urls.Select(url => httpClient.GetAsync(url));3await Task.WhenAll(tasks); // May overwhelm the server
Key Takeaways
Task.WhenAllwaits for all tasks to complete- Tasks run concurrently, not sequentially
- Returns array of results for
Task<T>[] - First exception is thrown; check
.Exceptionfor all errors - Use for independent operations only
- Empty collections return empty arrays