Skip to content

C# Task.WhenAny — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about C# Task.WhenAny. We cover key concepts, practical examples, and best practices.

You send the same request to multiple servers and need the first response. Or you have a timeout and a long operation — whichever finishes first wins. Task.WhenAny returns the first task to complete, letting you act on it immediately.

Wrong

var result = await FetchPrimaryAsync(); // Takes 5 seconds
// Even if a faster replica would respond in 1 second, we wait for the primary.

Output: Waits 5 seconds for the primary, ignoring faster alternatives.

var primaryTask = FetchPrimaryAsync();
var secondaryTask = FetchSecondaryAsync();
var completedTask = await Task.WhenAny(primaryTask, secondaryTask);
var result = await completedTask; // First response wins

// Cancel the slower one
// (requires passing CancellationToken to both)

Output: Takes ~1 second instead of 5. The first server to respond provides the result.

Timeout pattern:

var workTask = LongOperationAsync(ct);
var timeoutTask = Task.Delay(5000, ct);
var completed = await Task.WhenAny(workTask, timeoutTask);

if (completed == timeoutTask)
{
    throw new TimeoutException("Operation timed out");
}
return await workTask;

Prevention

  • Use Task.WhenAny for race conditions between multiple data sources.
  • Use Task.WhenAny for timeout patterns (combine with Task.Delay).
  • Always await the completed task after WhenAny returns — it may have faulted.
  • Cancel the slower tasks after WhenAny — otherwise they continue running.
  • Use Task.WhenAny with a cancellation token to avoid resource leaks.
  • Do not use Task.WhenAny when you need all results — use Task.WhenAll.

Common Mistakes with task when any

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

These mistakes appear frequently in real-world CSHARP code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

Does WhenAny cancel the remaining tasks automatically?

No. Task.WhenAny only tells you which task finished first. The other tasks continue running. You must cancel them explicitly using a shared CancellationTokenSource. Always cancel remaining tasks to prevent resource leaks.

Can I use WhenAny for progress reporting?

Yes. A common pattern is a loop with WhenAny on a remaining-set list. Remove completed tasks from the list after each WhenAny and continue until the list is empty. This processes tasks as they complete rather than in order.

What happens if the first completed task faulted?

Task.WhenAny returns the faulted task. When you await it, the exception is thrown. The other tasks may still be running. Handle this by checking completedTask.IsFaulted before awaiting, or use try-catch around the await.

Task.WhenAny is used in Doda Browser for replica selection — contacting multiple mirrors and using the first response. For more C# async, visit DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro