使用下面的代码我希望字符串“Finished”出现在控制台上的“Ready”之前。有人可以向我解释,为什么等待不等待完成这个样本中的任务?
static void Main(string[] args)
{
TestAsync();
Console.WriteLine("Ready!");
Console.ReadKey();
}
private async static void TestAsync()
{
await DoSomething();
Console.WriteLine("Finished");
}
private static Task DoSomething()
{
var ret = Task.Run(() =>
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(100);
}
});
return ret;
}
答案 0 :(得分:28)
你在“准备好”之后看到“完成”的原因!是因为异步方法常见的混淆点,与SynchronizationContexts无关。 SynchronizationContext控制哪些线程运行,但'async'有自己非常具体的有关排序的规则。程序会变得疯狂! :)
'await'确保当前异步方法中的其余代码在等待完成之后才会执行。它不会对来电者做出任何承诺。
您的异步方法返回'void',它适用于不允许原始调用方依赖方法完成的异步方法。如果您希望调用者也等待,您需要确保异步方法返回Task
(如果您只想要观察完成/例外),或者Task<T>
如果您确实想要也返回一个值。如果将方法的返回类型声明为这两者中的任何一个,那么编译器将负责其余的工作,关于生成表示该方法调用的任务。
例如:
static void Main(string[] args)
{
Console.WriteLine("A");
// in .NET, Main() must be 'void', and the program terminates after
// Main() returns. Thus we have to do an old fashioned Wait() here.
OuterAsync().Wait();
Console.WriteLine("K");
Console.ReadKey();
}
static async Task OuterAsync()
{
Console.WriteLine("B");
await MiddleAsync();
Console.WriteLine("J");
}
static async Task MiddleAsync()
{
Console.WriteLine("C");
await InnerAsync();
Console.WriteLine("I");
}
static async Task InnerAsync()
{
Console.WriteLine("D");
await DoSomething();
Console.WriteLine("H");
}
private static Task DoSomething()
{
Console.WriteLine("E");
return Task.Run(() =>
{
Console.WriteLine("F");
for (int i = 1; i < 10; i++)
{
Thread.Sleep(100);
}
Console.WriteLine("G");
});
}
在上面的代码中,“A”到“K”将按顺序打印出来。这是正在发生的事情:
“A”:在调用任何其他内容之前
“B”:正在调用OuterAsync(),Main()仍在等待。
“C”:正在调用MiddleAsync(),OuterAsync()仍在等待看看MiddleAsync()是否完整。
“D”:正在调用InnerAsync(),MiddleAsync()仍在等待看看InnerAsync()是否完整。
“E”:正在调用DoSomething(),InnerAsync()仍在等待DoSomething()是否完成。它会立即返回一个任务,该任务以 parallel 开始。
由于并行性,在InnerAsync()完成对DoSomething()返回的任务的完整性测试以及实际启动的DoSomething()任务之间存在竞争。
一旦DoSomething()启动,它会输出“F”,然后再睡一秒钟。
同时,除非线程调度超级混乱,否则InnerAsync()几乎肯定已经意识到DoSomething() 尚未完成 。现在,异步魔法开始了。
InnerAsync()将自己从callstack中拉出来,并说它的任务不完整。 这会导致MiddleAsync()从callstack中退出并说它自己的任务不完整。 这导致OuterAsync()将自己从callstack中拉出来,并说它的任务也是不完整的。
任务返回Main(),注意到它不完整,Wait()调用开始。
同时... 的
在该并行线程上,在DoSomething()中创建的旧式TPL任务最终完成休眠。它打印出“G”。
一旦该任务被标记为完成,其余的InnerAsync()将在TPL上进行调度以再次执行,并打印出“H”。这样就完成了InnerAsync()最初返回的任务。
任务标记完成后,其余的MiddleAsync()将在TPL上进行调度以再次执行,并打印出“I”。这完成了MiddleAsync()最初返回的任务。
一旦 任务标记为完成,其余的OuterAsync()就会在TPL上进行调度以再次执行,并打印出“J”。这样就完成了OuterAsync()最初返回的任务。
由于OuterAsync()的任务现在完成,Wait()调用返回,Main()打印出“K”。
因此,即使在顺序中有一点并行性,C#5异步仍然保证控制台写入按照确切的顺序发生。
让我知道这是否仍然令人困惑:)
答案 1 :(得分:16)
您在控制台应用中,因此您没有专门的SynchronizationContext
,这意味着您的延续将在线程池线程上运行。
此外,您还没有等待TestAsync()
中Main
的来电。这意味着当您执行此行时:
await DoSomething();
TestAsync
方法将控制权返回给Main
,它只是继续正常执行 - 即它输出“Ready!”并等待按键。
与此同时,DoSomething
完成后的第二秒,await
中的TestAsync
将继续在线程池线程中输出“已完成”。
答案 2 :(得分:1)
正如其他人所说,控制台程序使用默认的SynchronizationContext
,因此await
创建的延续被安排到线程池。
您可以使用Nito.AsyncEx library中的AsyncContext
来提供简单的异步上下文:
static void Main(string[] args)
{
Nito.AsyncEx.AsyncContext.Run(TestAsync);
Console.WriteLine("Ready!");
Console.ReadKey();
}