Why should I prefer single 'await Task.WhenAll' over multiple awaits?

avo picture avo · Aug 19, 2013 · Viewed 79.3k times · Source

In case I do not care about the order of task completion and just need them all to complete, should I still use await Task.WhenAll instead of multiple await? e.g, is DoWork2 below a preferred method to 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();
        }
    }
}

Answer

usr picture usr · Aug 19, 2013

Yes, use WhenAll because it propagates all errors at once. With the multiple awaits, you lose errors if one of the earlier awaits throws.

Another important difference is that WhenAll will wait for all tasks to complete even in the presence of failures (faulted or canceled tasks). Awaiting manually in sequence would cause unexpected concurrency because the part of your program that wants to wait will actually continue early.

I think it also makes reading the code easier because the semantics that you want are directly documented in code.