How do I get a return value from Task.WaitAll in a console app?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Task.WaitAll waits for tasks to finish, but it does not return their results. That is because the method itself returns void. If you want values back, the tasks must be Task<T>, and you read the results from those task objects after they complete.
In modern C#, the better answer is usually await Task.WhenAll(...), which returns an array of results for Task<T> inputs and avoids blocking the current thread. WaitAll still works, but it is rarely the best API in new code.
Why Task.WaitAll Does Not Return Values
Consider this example:
WaitAll only guarantees completion. The results live on task1 and task2, because each Task<int> carries its own eventual value.
Use Task<T> and Read .Result After Waiting
If you are in a synchronous console app and must block, this is the basic pattern:
Reading .Result after WaitAll is safe from a blocking standpoint because the tasks have already completed.
It is still worth remembering that this is synchronous waiting. Your console app may be fine with that, but the pattern does not scale as well as fully asynchronous composition.
Prefer Task.WhenAll in Modern Code
If you can use async Main, Task.WhenAll is cleaner because it gives you the values directly:
This avoids thread blocking and composes more naturally with other asynchronous code.
Aggregate Results from Many Tasks
For a dynamic number of tasks, keep them in a collection:
This is much cleaner than manually indexing each task after a WaitAll.
Handle Exceptions Correctly
Both WaitAll and WhenAll propagate exceptions, but the shape is slightly different. WaitAll throws an AggregateException, while await Task.WhenAll(...) unwraps in a way that is often easier to work with.
In real programs, exception behavior is another reason to favor await over blocking waits.
It also makes cancellation and timeout handling easier, because async code composes better with CancellationToken and other modern APIs.
Common Pitfalls
- Expecting
Task.WaitAllitself to return values. It never does. - Using
Taskinstead ofTask<T>when the operation should produce a result. - Calling
.Resultbefore completion, which can block unnecessarily. - Preferring blocking waits in code that could use
async Mainandawait Task.WhenAll. - Ignoring exceptions from one task while reading results from the others. Completion does not imply success.
Summary
- '
Task.WaitAllwaits for completion but returns no values.' - Use
Task<T>and read.Resultfrom each task after waiting. - In new code, prefer
await Task.WhenAll(...)because it returns the results directly. - Store many tasks in an array or list when aggregating values.
- Handle exceptions deliberately, especially when several tasks run in parallel.

