异步等待行为

3

我正在尝试使用async-await,并遇到了一种相当奇怪的行为,至少对我来说是这样。
我创建了三个模拟长时间运行任务的方法。
考虑这两个按钮点击处理程序:
对于button1_click,经过的时间大约为6000ms,而对于button2_click,则为约3000ms。
我无法理解为什么会发生这种情况,即6000ms与3000ms之间的差异。

    private async Task<string> TaskOne()
    {
        await Task.Delay(1000);
        return "task one";
    }

    private async Task<string> TaskTwo()
    {
        await Task.Delay(2000);
        return "task two";
    }

    private async Task<string> TaskThree()
    {
        await Task.Delay(3000);
        return "task three";
    }
    
    //time elapsed = 6000+ms
    private async void button1_Click(object sender, EventArgs e)
    {
        var watch = new Stopwatch();
        watch.Start();

        await TaskOne();
        await TaskTwo();
        await TaskThree();
        
        watch.Stop();
        textBox3.Text = watch.ElapsedMilliseconds.ToString();
    }

    //time elapsed = 3000+ms
    private async void button2_Click(object sender, EventArgs e)
    {
        var watch = new Stopwatch();
        watch.Start();

        var taskOne = TaskOne();
        var taskTwo = TaskTwo();
        var taskThree = TaskThree();

        await taskOne;
        await taskTwo;
        await taskThree;
        
        watch.Stop();
        textBox3.Text = watch.ElapsedMilliseconds.ToString();
    }

2
在第一种情况下,您不会在前一个任务完成之前开始下一个任务。在第二种情况下,您启动所有任务而不在它们之间等待,因此它们并行运行。 - Raymond Chen
2
我在另一个问题上已经给出了解释:https://dev59.com/NmYq5IYBdhLWcg3weQgy#44204614 - Joe Phillips
1
这篇文章是关于这个主题的好读物:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/ - devNull
2个回答

7
在这种情况下:
 await TaskOne();
 await TaskTwo();
 await TaskThree();

TaskTwo()必须等待TaskOne()完成后才能开始,这是因为你在等待它。同样地,由于等待,TaskThree()只有在TaskTwo()完成后才能开始。

接下来:

var taskOne = TaskOne();
var taskTwo = TaskTwo();
var taskThree = TaskThree();

await taskOne;
await taskTwo;
await taskThree;

您同时启动了所有 3 个任务,然后等待它们完成。这就是为什么它只花费了最长运行时间的原因。您会惊讶于有多少人不理解 async/await 中的这一点。如果这些任务不相互依赖,那么这种方式就是可行的。


7

摘要

需要注意的是,await 意味着真正的等待,这是一个常见的误解。


等待运算符 (C# 参考)

强调为我所加

等待运算符暂停执行封闭的异步方法, 直到与其操作数表示的异步操作完成 。 当异步操作完成时,等待运算符返回操作结果(如果有)。

当等待运算符应用于表示已经完成的操作的操作数时, 它立即返回该操作的结果,而不会暂停封闭方法

等待运算符不会阻塞评估异步方法的线程。 当等待运算符挂起封闭的异步方法时,控制权将返回方法的调用者。


下面是发生的事情。在您的第一个示例中,您依次启动并等待每个任务完成。 也就是说,在叫某人做某事并完成之前,再叫另一个人做某件事,以此类推。

await TaskOne();    // start, do something and wait for it
await TaskTwo();    // start, do something and wait for it
await TaskThree();  // start, do  something and wait for it

你的第二个例子,本质上是启动三个任务(hot),然后等待它们一个接一个地完成。也就是说,它们并行运行,并按顺序等待。

例如,你告诉三个朋友去做事情,然后等待第一个回来,然后是第二个和第三个。这样更有效率...没有烦人的朋友一直等到前一个回来。

即使第二个任务第一个任务之前完成,你仍然需要等待第一个任务才能查看第二个任务的完成状态,以此类推。

var taskOne = TaskOne();     // start, do something
var taskTwo = TaskTwo();     // start, do something
var taskThree = TaskThree(); // start, do something

// all 3 tasks are started

await taskOne;   // wait for it
await taskTwo;   // wait for it
await taskThree; // wait for it

或者您可以使用Task.WhenAll进行类似操作。

创建一个任务,该任务在所有提供的任务完成时完成。

var taskOne = TaskOne();     // start, do something
var taskTwo = TaskTwo();     // start, do something
var taskThree = TaskThree(); // start, do something

// wait for them all to finish!
await Task.WhenAll(taskOne, taskTwo, taskThree);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接