How it works
WhenAll produces one task representing completion of every supplied task. If more than one input fails, the combined task retains their failure information. Awaiting the combined task propagates a failure, but catching that one propagated exception is not the same as inspecting every exception recorded by the group.
Both input tasks in this sample are already faulted, making the result deterministic without external work. The catch prints a group-level message. The combined task's Exception property is an AggregateException whose inner collection contains two failures, and IsFaulted confirms the group's terminal state.
A production workflow may need both a simple user-facing summary and detailed per-operation diagnostics. Keep the task or attach identifiers to individual results so failures can be traced back to their source. An arbitrary exception message alone is a poor substitute for an explicit record of which operation failed.
WhenAll waits for the supplied operations to finish; it does not automatically cancel siblings because one task faults. If the workflow wants cooperative early cancellation, it must own and signal an appropriate cancellation source, and still account for all started tasks. Grouping completion and controlling operation lifetime are different responsibilities.
Practice by replacing one failing input with Task.FromResult(7), then replacing both with successful tasks. Predict the group's status and whether Exception is null in each case. Never dereference Exception unconditionally in code that also accepts successful or canceled completion.
Finally, add a canceled input beside a faulted input and inspect the documented precedence of the combined state. Use that experiment to design a report distinguishing successful results, cancellation, and faults. Do not turn every failure into a default value solely to make WhenAll appear successful; preserve the information callers need to decide whether the overall workflow is usable.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
Task<int> first = Task.FromException<int>(new InvalidOperationException("first"));
Task<int> second = Task.FromException<int>(new ArgumentException("second"));
Task<int[]> combined = Task.WhenAll(first, second);
try { await combined; }
catch (Exception) { Console.WriteLine("group failed"); }
Console.WriteLine(combined.Exception!.InnerExceptions.Count);
Console.WriteLine(combined.IsFaulted);
Expected output
group failed 2 True
Keep the combined task when you need to inspect every failure, not just the exception propagated by await.