Task.WhenAny - First Completion
Task.WhenAny returns when any one of the provided tasks completes. It's useful for timeouts, cancellation, and racing operations.
Basic Usage
csharp1Task<string> task1 = FetchFromServer1Async(); // Takes 2 seconds2Task<string> task2 = FetchFromServer2Async(); // Takes 1 second3Task<string> task3 = FetchFromServer3Async(); // Takes 3 seconds45// Returns when ANY task completes6Task<string> firstCompleted = await Task.WhenAny(task1, task2, task3);78// firstCompleted is task2 (fastest)9string result = await firstCompleted;10Console.WriteLine($"First result: {result}");
Important: WhenAny Returns the Task, Not the Result
csharp1// WhenAny returns Task<Task<T>>, not Task<T>2Task<Task<int>> whenAnyTask = Task.WhenAny(task1, task2, task3);3Task<int> completedTask = await whenAnyTask;4int result = await completedTask; // Get actual result56// Or simplified:7Task<int> first = await Task.WhenAny(task1, task2, task3);8int result = await first;
Timeout Pattern
A common use case is implementing timeouts:
csharp1async Task<string> FetchWithTimeoutAsync(string url, TimeSpan timeout)2{3 Task<string> fetchTask = httpClient.GetStringAsync(url);4 Task delayTask = Task.Delay(timeout);56 Task completedTask = await Task.WhenAny(fetchTask, delayTask);78 if (completedTask == delayTask)9 {10 throw new TimeoutException($"Request to {url} timed out");11 }1213 return await fetchTask;14}1516// Usage17try18{19 string data = await FetchWithTimeoutAsync("https://api.example.com",20 TimeSpan.FromSeconds(5));21}22catch (TimeoutException)23{24 Console.WriteLine("The request took too long");25}
First Successful Response
Get the first successful result, ignoring failures:
csharp1async Task<string> FetchFromAnyServerAsync(string[] serverUrls)2{3 var tasks = serverUrls.Select(url => FetchAsync(url)).ToList();45 while (tasks.Count > 0)6 {7 Task<string> completed = await Task.WhenAny(tasks);8 tasks.Remove(completed);910 try11 {12 return await completed; // Return first success13 }14 catch15 {16 // This server failed, try next17 if (tasks.Count == 0)18 throw; // All failed19 }20 }2122 throw new InvalidOperationException("No servers available");23}
Progress Reporting
Process tasks as they complete:
csharp1async Task ProcessAsCompletedAsync(List<Task<int>> tasks)2{3 var remaining = new List<Task<int>>(tasks);45 while (remaining.Count > 0)6 {7 Task<int> completed = await Task.WhenAny(remaining);8 remaining.Remove(completed);910 try11 {12 int result = await completed;13 Console.WriteLine($"Completed: {result}, Remaining: {remaining.Count}");14 }15 catch (Exception ex)16 {17 Console.WriteLine($"Failed: {ex.Message}, Remaining: {remaining.Count}");18 }19 }20}
Racing Multiple Implementations
Choose the fastest implementation:
csharp1async Task<SearchResults> SearchAsync(string query)2{3 var googleTask = SearchGoogleAsync(query);4 var bingTask = SearchBingAsync(query);5 var duckTask = SearchDuckDuckGoAsync(query);67 // Return whichever responds first8 var winner = await Task.WhenAny(googleTask, bingTask, duckTask);9 return await winner;10}
Cancellation with WhenAny
Cancel remaining tasks when one completes:
csharp1async Task<string> FetchFirstWithCancellationAsync(string[] urls)2{3 using var cts = new CancellationTokenSource();4 var token = cts.Token;56 var tasks = urls.Select(url =>7 FetchAsync(url, token)).ToList();89 var first = await Task.WhenAny(tasks);1011 // Cancel remaining tasks12 cts.Cancel();1314 return await first;15}
Comparison: WhenAny vs WhenAll
| WhenAny | WhenAll |
|---|---|
| Completes when first task finishes | Completes when all tasks finish |
| Returns the completed task | Returns array of results |
| Good for timeouts, racing | Good for parallel execution |
| Other tasks continue running | All tasks must complete |
WhenAny Doesn't Cancel Other Tasks
Important: When one task completes, others keep running:
csharp1var task1 = LongRunningAsync(); // Takes 10 seconds2var task2 = QuickAsync(); // Takes 1 second34var first = await Task.WhenAny(task1, task2);5// task1 is STILL RUNNING in the background!67// If you don't want this, use cancellation tokens
Implementing Retry with Delay
csharp1async Task<T> RetryWithDelayAsync<T>(Func<Task<T>> operation,2 int maxRetries,3 TimeSpan delay)4{5 for (int i = 0; i < maxRetries; i++)6 {7 try8 {9 return await operation();10 }11 catch when (i < maxRetries - 1)12 {13 await Task.Delay(delay);14 }15 }1617 return await operation(); // Last attempt, let exception propagate18}
Key Takeaways
WhenAnycompletes when the first task finishes- Returns
Task<Task<T>>- need to await twice to get result - Perfect for timeout patterns with
Task.Delay - Other tasks continue running after WhenAny returns
- Use cancellation tokens to stop remaining tasks
- Useful for racing, progress reporting, and fallbacks