几乎相同的方法中,async/await 的行为不同。

23

假设我有两个异步方法

public async static Task RunAsync1()
{
    await Task.Delay(2000);
    await Task.Delay(2000);
}

以及

public async static Task RunAsync2()
{
    var t1 = Task.Delay(2000);
    var t2 = Task.Delay(2000);

    await t1;
    await t2;
}

然后我像这样使用它

public static void M()
{
    RunAsync1().GetAwaiter().GetResult();
    RunAsync2().GetAwaiter().GetResult();
}

在结果中,RunAsync1将会运行4秒,但是RunAsync2只需要2秒
有人可以解释一下原因吗?这两个方法几乎相同。有何区别?

20
这是一道面试题吗?这个经典的例子展示了异步代码如何工作... - BlueRaja - Danny Pflughoeft
@BlueRaja-DannyPflughoeft 不是的。这只是我们在代码库中使用的简化版本。您能否请指出您在哪里看到了这段代码的演示? - Roman Marusyk
1
@MegaTron 这是关于基本 async 行为的众多讨论之一(链接:https://dev59.com/g2kw5IYBdhLWcg3wbqAS#9899487)。 - JSteward
4个回答

58

第二种方法同时启动了两个任务。它们将同时完成(因为它们在并行运行)。在第一种方法中,您首先运行一个方法(2秒钟),等待它完成,然后启动第二个方法(再等待2秒钟)。关键点在于Task.Delay(..)在您调用时立即开始,而不是在您等待它时开始。

更明确地说,第一种方法:

var t1 = Task.Delay(2000); // this task is running now
await t1; // returns 2 seconds later
var t2 = Task.Delay(2000); // this task is running now
await t2; // returns 2 more seconds later

第二种方法:

var t1 = Task.Delay(2000); 
var t2 = Task.Delay(2000); // both are running now

await t1; // returns in about 2 seconds
await t2; // returns almost immediately, because t2 is already running for 2 seconds

10
重点不在于变量,而在于何时何地使用 await。如果你执行了 var t1 = Task.Delay(2000); 然后执行 await t1;,你将得到相同的结果。 - MoonKnight

14

仅仔细检查您的代码:

public async static Task RunAsync1()
{
    await Task.Delay(2000); // Start a delay task, and WAIT for it to finish
    await Task.Delay(2000); // Start a delay task, and WAIT for it to finish
}

因此,第二个await Task.Delay(2000);在第一个调用完成后(即2秒后)调用。

而第二个方法,

public async static Task RunAsync2()
{
    var t1 = Task.Delay(2000); // Start a task
    var t2 = Task.Delay(2000); // Start a task

    await t1; // Wait for task to finish
    await t2; // Wait for task to finish
}

因此,任务t1和t2同时运行。

如果你将它改成

public async static Task RunAsync3()
{
    var t1 = Task.Delay(2000); // Start a task
    await t1; // Wait for task to finish

    var t2 = Task.Delay(2000); // Start a task
    await t2; // Wait for task to finish
}

您将获得与RunAsync1中相同的结果。


6
在第一种情况下,您表示
public async static Task RunAsync1()
{
    var t1 = Task.Delay(2000);
    await t1;
    var t2 = await Task.Delay(2000);
    await t2;
}

这相当于

  1. 0:00 在2秒内创建一个回调函数 0:00
  2. 0:00 等待回调函数返回 0:02
  3. 0:02 在2秒内创建一个回调函数 0:02
  4. 0:02 等待回调函数返回 0:04
  5. 0:04 返回;

第二种情况是

public async static Task RunAsync2()
{
    var t1 = Task.Delay(2000);
    var t2 = Task.Delay(2000);

    await t1;
    await t2;
}
  1. 0:00 在2秒钟内创建回调函数 0:00
  2. 0:00 在2秒钟内创建回调函数 0:00
  3. 0:00 等待第一个回调函数 0:02
  4. 0:02 等待第二个回调函数 0:02
  5. 0:02 返回结果

换句话说,第一个是顺序异步编程,第二个是并行异步编程。


第二个 4. 不应该是 0:00 等待第二个回调 0:02 吗? - camden_kid
1
@camden_kid:不是的,因为在第三步之后,您已经到达了0:02(您已经在那里等待了2秒钟)。 - hoffmale
@hoffmale 写法有点令人困惑。在0.02时,它并没有真正等待,因为它刚刚完成了等待。 - camden_kid
1
@camden_kid:在第3步中,您正在等待任务1完成。这意味着要等到0:02,因为那时任务1即将完成。在第4步中,您正在等待任务2完成。恰好发生的是,任务2正在那一刻结束,所以没有太多延迟。“等待任务完成”可以意味着“如果该任务已经完成,则立即返回”。 - hoffmale
@hoffmale 好的,谢谢。 - camden_kid

5
无论何时开始一个任务,它已经在创建时开始了,而不是在调用await时开始。
如果你创建一个任务并将其放入变量中,当你等待它时,它可能已经完成了。这就是发生在你的第二个案例中的情况。await只是确保在继续之前必须完成。

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