Start here. This is the direct spoken answer to practice first.
Why this question matters
Compare Parallel.ForEachAsync and Task.WhenAll for processing collections with async work and controlled concurrency. It matters in real backend work because load, cancellation, retries, and dependency latency decide whether the API stays predictable under pressure. The practical angle is parallel processing, bounded concurrency, collection processing, async I/O, CPU work, and dependency protection, tied to a concrete production decision.
Task.WhenAll waits for a supplied set of tasks and combines their completion; it does not impose a concurrency limit, and those tasks may already be running. It is convenient for a small, fixed fan-out. Parallel.ForEachAsync is useful for a larger collection when I want an explicit maximum degree of parallelism. The limit comes from dependency and resource capacity, not from choosing the highest number that finishes a test quickly.