这两个等待呼叫有什么区别

时间:2019-12-11 16:42:21

标签: c# .net async-await

我有以下代码:

async Task Main()
{
    Stopwatch sw = new Stopwatch();

    // Variante 1
    sw.Start();
    var m1 = await Task1();
    var m2 = await Task2();
    var m3 = await Task3();
    Console.WriteLine(m1);
    Console.WriteLine(m2);
    Console.WriteLine(m3);
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);

    // Variante 2
    sw.Restart();
    var t1 = Task1();
    var t2 = Task2();
    var t3 = Task3();
    m1 = await t1;
    m2 = await t2;
    m3 = await t3;
    Console.WriteLine(m1);
    Console.WriteLine(m2);
    Console.WriteLine(m3);
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

// Define other methods, classes and namespaces here
public async Task<string> Task1()
{
    await Task.Delay(5000);
    return "Task1 ready";
}   

public async Task<string> Task2()
{
    await Task.Delay(5000);
    return "Task2 ready";
}

public async Task<string> Task3()
{
    await Task.Delay(5000);
    return "Task3 ready";
}

第一部分产生将近15000ms,第二部分产生5000ms,但是我不明白为什么!

在第一部分中,我等待返回任务的异步方法,在第二部分中,我获得此任务并等待任务。

我想念什么?

5 个答案:

答案 0 :(得分:5)

在变体2中,在您调用await之前,所有三个任务都已开始。在变体1中,您等待每个完成之前,再开始下一个。

答案 1 :(得分:4)

其他答案部分正确,但是我想编写自己的答案以清除某些内容。

任务不是并行启动的。

严格来说,“并行”表示正在同时评估两行代码。这里不会发生这种情况。

每个异步方法都开始同步运行。当await对不完整的Task起作用时(例如,当启动I / O请求时),魔术就发生了。此时,方法返回自身不完整的Task,并且该方法的继续计划在以后进行。

执行此操作时:

var t1 = Task1();
var t2 = Task2();
var t3 = Task3();

会发生这种情况:

  1. Task1开始执行。
  2. 在第一个await处,Task1返回一个Task
  3. Task2开始执行。
  4. 在第一个await处,Task2返回一个Task
  5. Task3开始执行。
  6. 在第一个await处,Task3返回一个Task

因此使运行速度更快的原因是,您的代码使用了Task1等待响应的时间来启动Task2中的请求。


这些任务可能 continuations 根据情况并行运行。

在存在同步上下文(如ASP.NET)的情况下,延续必须回到相同的上下文,并且没有任何并行运行。这意味着Task1的执行将不会继续,直到在该上下文中没有其他内容运行为止。在您的代码中,发生在以下位置:

m1 = await t1;

只有在该行之后,当前上下文才被释放,Task1的延续可以在该上下文中运行,完成后,await t1之后的所有内容都将放入“待办事项”列表中完成。

如果您在没有同步上下文的情况下运行(例如ASP.NET Core),或者您指定.ConfigureAwait(false)告诉它不需要返回相同的上下文,则任务的延续将在ThreadPool线程上运行。这意味着所有三个任务的延续可以在不同的线程上并行运行。如果发生这种情况,那么当您按下await t1时,就已经可以完成了。


如果您这样做:

var m1 = await Task1();
var m2 = await Task2();
var m3 = await Task3();

您要等到Task1完全完成后才能开始Task2。根据您的应用程序,仍然有好处。例如,在ASP.NET中,它释放供其他不相关请求使用的线程。在桌面应用程序中,它可以释放UI线程以响应用户输入。

答案 2 :(得分:1)

调用该方法后,Property 'file' does not exist on type '{children?:ReactNode; }' 就开始运行。您正在呼叫TaskTask1Task2而不等待它们中的任何一个,因此它们都同时开始运行。

在第二部分中,您正在调用Task3,然后等待,然后再调用Task1Task2。这意味着Task3直到Task2完成才开始,并且Task1直到Task3完成才开始。他们一个接一个地运行,从而延长了总运行时间。

答案 3 :(得分:1)

在第一个代码块中,您启动每个任务并等待其完成,然后再移至下一个任务并执行相同的任务,它们按顺序运行,其中下一个任务仅在上一个任务完成后才开始。将此视为接力赛,每个赛车手必须等待上一个赛车手完成才能开始。

在第二个代码块中,您首先启动这三个任务中的每一个,然后等待它们一步一步完成。它们并行运行是因为您几乎同时启动了它们。就像一场比赛,每个人都在同一时间开始,然后等待他们完成。

答案 4 :(得分:-1)

这里的问题是您将JIT编译包括在您的度量中。

您是否尝试过将第一个选项切换为第二个以查看其效果?

如果您查看the compiler generated code,将会发现两者之间没有显着差异。

要有效地衡量效果,您需要使用BenchMarkDotNet之类的东西。