Hard20 minDesign Patterns
UpdatedAug 1, 2026
Edit

Task.WhenAll vs Parallel.ForEach

Question Variations

  • "When would you use `Task.WhenAll` versus `Parallel.ForEach`?"
  • "Explain the difference between I/O-bound and CPU-bound operations in the context of .NET."
  • "What is 'thread pool starvation,' and how can using `Parallel.ForEach` for I/O-bound work cause it?"
  • "What is `Parallel.ForEachAsync`, and when was it introduced in the .NET ecosystem?"

Why This Is Asked

This distinguishes developers who understand the difference between I/O concurrency and CPU parallelism. Interviewers want to see if you can pick the right tool — async for I/O-bound work, parallel for CPU-bound work — and avoid thread pool starvation.

Key Concepts

  • Task.WhenAll: I/O concurrency — coordinates multiple async operations, does not necessarily use extra threads
  • Parallel.ForEach: CPU parallelism — partitions data across threads for compute-heavy work
  • Parallel.ForEachAsync (.NET 6+): hybrid — async I/O with controlled concurrency via MaxDegreeOfParallelism
  • Using Parallel.ForEach for I/O-bound work blocks threads and leads to thread pool starvation
  • Shared state in parallel operations requires thread-safe collections or explicit locking

Question Variations

  • “When would you use Task.WhenAll versus Parallel.ForEach?”
  • “Explain the difference between I/O-bound and CPU-bound operations in the context of .NET.”
  • “What is ‘thread pool starvation,’ and how can using Parallel.ForEach for I/O-bound work cause it?”
  • “What is Parallel.ForEachAsync, and when was it introduced in the .NET ecosystem?”

Answers by Technology

+ Add Variant
.NETImprove this answer ✏️

Expected Answer (.NET 10 / C# 14)

.NET provides different mechanisms for handling concurrency depending on whether the task is I/O-bound or CPU-bound:

  • Task.WhenAll: Used for Asynchronous Concurrency. It coordinates multiple I/O-bound tasks (e.g., calling three APIs). It is extremely efficient because it doesn’t block threads while waiting for results.
  • Parallel.ForEach: Used for Data Parallelism. It partitions a collection and processes items in parallel on multiple threads. It is designed for CPU-bound work (e.g., image processing).
  • Parallel.ForEachAsync (.NET 6+): A hybrid that allows processing a collection asynchronously while controlling the MaxDegreeOfParallelism. Ideal for high-volume I/O tasks like sending 10,000 emails in batches of 20.

Why It Matters

Using the wrong tool can cripple performance. Using Parallel.ForEach (CPU-bound) for I/O tasks will block thread pool threads unnecessarily, leading to Thread Pool Starvation. Conversely, using Task.WhenAll for heavy computation can make the application unresponsive by overwhelming the task scheduler with long-running tasks that don’t yield control.

Example Code

// 1. Asynchronous I/O Concurrency
var tasks = urls.Select(url => _httpClient.GetStringAsync(url));
string[] results = await Task.WhenAll(tasks);

// 2. CPU-bound Parallelism
Parallel.ForEach(largeDataset, item => {
    ComputeComplexMath(item);
});

// 3. Rate-limited Asynchronous Processing
await Parallel.ForEachAsync(emails, new ParallelOptions { MaxDegreeOfParallelism = 10 }, 
    async (email, token) => {
        await _emailService.SendAsync(email);
    });

Common Mistakes

  • Blocking in Parallel loops: Using synchronous I/O inside Parallel.ForEach. This locks up multiple threads for the duration of the I/O.
  • Ignoring Thread Safety: Modifying a shared List<T> inside a Parallel.ForEach without locking or using ConcurrentBag<T>.
  • Overwhelming Downstream Systems: Using Task.WhenAll on 1,000 API calls simultaneously, which can trigger rate limits or exhaust socket connections.

Follow-up Questions

  • What is “Thread Pool Starvation”? (Answer: When all threads in the pool are blocked or busy, and new tasks cannot start because there are no available threads).
  • How do you cancel a Parallel.ForEach loop? (Answer: By checking the ParallelLoopState.Stop() method or passing a CancellationToken in the case of Parallel.ForEachAsync).

References