(Disclaimer: This answer is taken / inspired from TPL Async by Ian Griffiths on Pluralsight )
Another reason to prefer WhenAll is exception handling.
Suppose you have a try-catch block for your DoWork methods, and suppose that they called different DoTask methods:
static async Task DoWork1() // modified with try-catch { try { var t1 = DoTask1Async("t1.1", 3000); var t2 = DoTask2Async("t1.2", 2000); var t3 = DoTask3Async("t1.3", 1000); await t1; await t2; await t3; Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result)); } catch (Exception x) { // ... } }
In this case, if all 3 tasks throw exceptions, only the first one will be caught. Any subsequent exception will be lost. Those. if t2 and t3 throw an exception, then only t2 will be caught; etc. Exceptions for subsequent tasks will go unnoticed.
Where, as in WhenAll - if any or all of the tasks are not completed, the resulting task will contain all exceptions. The await keyword still always throws the first exception. Thus, other exceptions still go unnoticed. One way to overcome this is to add an empty continuation after the WhenAll task and put the wait there. Thus, in the event of a task failure, the result property will give a complete aggregation exception:
static async Task DoWork2() //modified to catch all exceptions { try { var t1 = DoTask1Async("t1.1", 3000); var t2 = DoTask2Async("t1.2", 2000); var t3 = DoTask3Async("t1.3", 1000); var t = Task.WhenAll(t1, t2, t3); await t.ContinueWith(x => { }); Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2])); } catch (Exception x) { // ... } }