我有以下代码:
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,但是我不明白为什么!
在第一部分中,我等待返回任务的异步方法,在第二部分中,我获得此任务并等待任务。
我想念什么?
答案 0 :(得分:5)
在变体2中,在您调用await
之前,所有三个任务都已开始。在变体1中,您等待每个完成之前,再开始下一个。
答案 1 :(得分:4)
其他答案部分正确,但是我想编写自己的答案以清除某些内容。
任务不是并行启动的。
严格来说,“并行”表示正在同时评估两行代码。这里不会发生这种情况。
每个异步方法都开始同步运行。当await
对不完整的Task
起作用时(例如,当启动I / O请求时),魔术就发生了。此时,方法返回自身不完整的Task
,并且该方法的继续计划在以后进行。
执行此操作时:
var t1 = Task1();
var t2 = Task2();
var t3 = Task3();
会发生这种情况:
Task1
开始执行。await
处,Task1
返回一个Task
。Task2
开始执行。await
处,Task2
返回一个Task
。Task3
开始执行。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; }'
就开始运行。您正在呼叫Task
,Task1
和Task2
而不等待它们中的任何一个,因此它们都同时开始运行。
在第二部分中,您正在调用Task3
,然后等待,然后再调用Task1
和Task2
。这意味着Task3
直到Task2
完成才开始,并且Task1
直到Task3
完成才开始。他们一个接一个地运行,从而延长了总运行时间。
答案 3 :(得分:1)
在第一个代码块中,您启动每个任务并等待其完成,然后再移至下一个任务并执行相同的任务,它们按顺序运行,其中下一个任务仅在上一个任务完成后才开始。将此视为接力赛,每个赛车手必须等待上一个赛车手完成才能开始。
在第二个代码块中,您首先启动这三个任务中的每一个,然后等待它们一步一步完成。它们并行运行是因为您几乎同时启动了它们。就像一场比赛,每个人都在同一时间开始,然后等待他们完成。
答案 4 :(得分:-1)
这里的问题是您将JIT编译包括在您的度量中。
您是否尝试过将第一个选项切换为第二个以查看其效果?
如果您查看the compiler generated code,将会发现两者之间没有显着差异。
要有效地衡量效果,您需要使用BenchMarkDotNet之类的东西。