Why should I give preference to a single "wait Task.WhenAll" for several expectations?

In case I don't care about the completion order of the task, and I just need them all to be completed, should I use await Task.WhenAll instead of several await ? For example, DoWork2 below the preferred method for DoWork1 (and why?):

 using System; using System.Threading.Tasks; namespace ConsoleApp { class Program { static async Task<string> DoTaskAsync(string name, int timeout) { var start = DateTime.Now; Console.WriteLine("Enter {0}, {1}", name, timeout); await Task.Delay(timeout); Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds); return name; } static async Task DoWork1() { var t1 = DoTaskAsync("t1.1", 3000); var t2 = DoTaskAsync("t1.2", 2000); var t3 = DoTaskAsync("t1.3", 1000); await t1; await t2; await t3; Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result)); } static async Task DoWork2() { var t1 = DoTaskAsync("t2.1", 3000); var t2 = DoTaskAsync("t2.2", 2000); var t3 = DoTaskAsync("t2.3", 1000); await Task.WhenAll(t1, t2, t3); Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result)); } static void Main(string[] args) { Task.WhenAll(DoWork1(), DoWork2()).Wait(); } } } 
+93
c # parallel-processing task-parallel-library async-await
Aug 19 '13 at 9:56 on
source share
5 answers

Yes, use WhenAll because it propagates all errors at the same time. With multiple waiting, you lose errors if one of the previous ones expects throws.

Another important difference is that WhenAll will wait for all tasks to complete even if there are failures (failed or canceled tasks). This causes unexpected concurrency, because the part of your program that wants to wait will actually continue early.

I think this also makes it easier to read the code, because the semantics you want are directly documented in the code.

+90
Aug 19 '13 at 10:02 on
source share

My understanding is that the main reason for the preference of Task.WhenAll for multiple await is the performance / task "task": the DoWork1 method does something like this:

  • start with the given context
  • keep context
  • wait t1
  • restore original context
  • keep context
  • wait t2
  • restore original context
  • keep context
  • wait t3
  • restore original context

In contrast, DoWork2 does the following:

  • start with a given context
  • keep context
  • wait for all t1, t2 and t3
  • restore original context

Whether this is a big enough deal for your particular case, of course, "depends on the context" (pardon of puns).

+22
Nov 27 '13 at 16:39
source share

The asynchronous method is implemented as a state machine. You can write methods so that they are not compiled into state machines, which is often called the fast asynchronous generation method. They can be implemented as follows:

 public Task DoSomethingAsync() { return DoSomethingElseAsync(); } 

When using Task.WhenAll you can maintain this fast-track code, while maintaining that the caller can wait for all tasks to complete, for example:

 public Task DoSomethingAsync() { var t1 = DoTaskAsync("t2.1", 3000); var t2 = DoTaskAsync("t2.2", 2000); var t3 = DoTaskAsync("t2.3", 1000); return Task.WhenAll(t1, t2, t3); } 
+14
Dec 24 '13 at 16:25
source share

Other answers to this question offer technical reasons why await Task.WhenAll(t1, t2, t3); is preferred. This answer will be aimed at looking at it from a softer side (as @usr refers to), and yet come to the same conclusion.

await Task.WhenAll(t1, t2, t3); it is a more functional approach because it declares intention and is atomic.

With await t1; await t2; await t3; await t1; await t2; await t3; await t1; await t2; await t3; nothing prevents the team mate (or perhaps even himself!) from adding code between the individual await statements. Of course, you squeezed it to one line to essentially achieve this, but this does not solve the problem. In addition, this is usually a bad form in the command settings, including several statements in a given line of code, as this can complicate scanning the source file for the human eye.

Simply put, await Task.WhenAll(t1, t2, t3); more convenient to maintain, as it more clearly reports your intentions and is less vulnerable to specific errors that may occur due to well-meaning code updates or even just erroneous mergers.

+3
Jan 06 '18 at 0:11
source share

(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) { // ... } } 
+3
May 05 '18 at 15:33
source share



All Articles