简体   繁体   中英

Why Multiple await take same time like Task.WhenAll()

I understand that when you have a list of tasks, it's recommended to use await Task.WhenAll() over multiple await , due to the way that Task.WhenAll() handles exceptions. However, from my understanding of the way that "async,await" works, I wonder why the below code blocks have the same execution time:

static void Main(string[] args)
{
    MainAsync(args).GetAwaiter().GetResult();
    Console.ReadLine();
}

static async Task MainAsync(string[] args)
{
    Console.WriteLine("Starts :" + DateTime.Now.ToLongTimeString());
    var firstTask = SleepForTime(10000);
    var secondTask = SleepForTime(7000);
    var thirdTask = SleepForTime(5000);

    await firstTask;
    await secondTask;
    await thirdTask;

    Console.WriteLine("Done :" + DateTime.Now.ToLongTimeString());

    Console.ReadLine();
}

public static async Task SleepForTime(int seconds)
{
    await Task.Delay(seconds);
}

This block will take 10 seconds to be executed, which is the same for this one:

static void Main(string[] args)
{
    MainAsync(args).GetAwaiter().GetResult();
    Console.ReadLine();
}

static async Task MainAsync(string[] args)
{
    Console.WriteLine("Starts :" + DateTime.Now.ToLongTimeString());
    var firstTask = SleepForTime(10000);
    var secondTask = SleepForTime(7000);
    var thirdTask = SleepForTime(5000);

    await Task.WhenAll(firstTask, secondTask, thirdTask);

    Console.WriteLine("Done :" + DateTime.Now.ToLongTimeString());

    Console.ReadLine();
}

public static async Task SleepForTime(int seconds)
{
    await Task.Delay(seconds);
}

From my understanding, the first block should take 22 seconds, because the list of await will get executed in order one by one, as this is how async,await explained by Microsoft in MSDN. What I'm missing here? Is it something optimised the the compiler? Could someone explain what's going under the hood?

Task.Delay internally uses a timer to notify the program when it should continue. The timer starts as soon as you call Task.Delay . In both cases you start the tasks one after another as you store the tasks in variables, only to await them later. While awaiting any of them, the timers are still going in the background and because they started more or less at the same time, they finish when the one with the longest delay finishes

var firstTask = SleepForTime(10000);
var secondTask = SleepForTime(7000);
var thirdTask = SleepForTime(5000);  

// All of the tasks are already started
Console.WriteLine("Start");
await firstTask;                       //this finishes after ~10s
Console.WriteLine("First finished");
await secondTask;                      //this finishes immediately
Console.WriteLine("Second finished");
await thirdTask;                       //this also finishes immediately
Console.WriteLine("Third finished");

All of the First/Second/Third finished messages show up almost at the same time, after 10s. You can see a change after some modification:

var firstTask = SleepForTime(5000);
var secondTask = SleepForTime(7000);
var thirdTask = SleepForTime(10000);  

// All of the tasks are already started
Console.WriteLine("Start");
await firstTask;                      //this finishes after ~5s
Console.WriteLine("First finished");
await secondTask;                     //this finishes after 2 more seconds
Console.WriteLine("Second finished");
await thirdTask;                      //this finishes after 3 more seconds
Console.WriteLine("Third finished");

Now First finished shows up after 5s, Second finished after another 2s and Third finished after another 3s.

To get the desired result you'd have to call the functions sequentially and await each one right there, like this:

Console.WriteLine("Start");
await SleepForTime(10000);            //this finishes after 10s
Console.WriteLine("First finished");
await SleepForTime(7000);             //this finishes after 7s
Console.WriteLine("Second finished");
await SleepForTime(5000);             //this finishes after 5s
Console.WriteLine("Third finished");

The SleepForTime function is a perfect candidate for some style improvements - use the suffix Async to indicate that it should be used in asynchronous code and you can return the task returned from Task.Delay itself making the code a little simpler (for you and the compiler)

public static Task SleepForTimeAsync(int seconds)
{
    return Task.Delay(seconds);
}

From my understanding, the first block should take 22 seconds, because the list of await will get executed in order one by one.

No. You are starting all 3 tasks before you await them, so they are all running at the same time.

var firstTask = SleepForTime(10000); //start the 1st Task
var secondTask = SleepForTime(7000); //start the 2nd Task
var thirdTask = SleepForTime(5000);  //start the 3rd Task

await firstTask;  //wait for the 1st Task to finish
await secondTask; //wait for the 2nd Task to finish
await thirdTask;  //wait for the 3rd Task to finish

The following would take 22 seconds:

await SleepForTime(10000);
await SleepForTime(7000);
await SleepForTime(5000);

The next Task isn't started until the previous one is finished.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM