Async /等待没有按预期反应

使用下面的代码我希望字符串“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; } 

你之所以在“准备好”之后看到“完成”的原因! 是因为异步方法常见的混淆点,与SynchronizationContexts无关。 SynchronizationContext控制哪些线程运行,但’async’有自己非常具体的有关排序的规则。 程序会变得疯狂! 🙂

‘await’确保当前异步方法中的其余代码在等待完成之后才会执行。 它不会对呼叫者做出任何承诺。

您的异步方法返回’void’,它适用于不允许原始调用方依赖方法完成的异步方法。 如果你希望你的调用者也等待,你需要确保你的异步方法返回一个Task (如果你只想要观察完成/exception),或者如果你真的想要返回一个值,还需要一个Task 。 如果将方法的返回类型声明为这两者中的任何一个,那么编译器将负责其余的工作,即生成表示该方法调用的任务。

例如:

 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()是否完成。 它会立即返回一个并行启动的任务。

由于并行性,InnerAsync()完成了对DoSomething()返回的任务的完整性测试以及实际启动的DoSomething()任务之间的竞争。

一旦DoSomething()启动,它打印出“F”,然后hibernate一秒钟。

同时,除非线程调度超级混乱,否则InnerAsync()几乎肯定已经意识到DoSomething() 尚未完成 。 现在异步魔法开始了。

InnerAsync()将自己从callstack中拉出来,并说它的任务是不完整的。 这会导致MiddleAsync()从callstack中退出并说它自己的任务不完整。 这会导致OuterAsync()从callstack中退出,并说它的任务也是不完整的。

任务返回到Main(),注意到它不完整,并且Wait()调用开始。

与此同时...

在该并行线程上,在DoSomething()中创建的旧式TPL任务最终完成hibernate。 它打印出“G”。

一旦该任务被标记为完成,其余的InnerAsync()将在TPL上进行调度以再次执行,并打印出“H”。 这样就完成了InnerAsync()最初返回的任务。

一旦任务被标记为完成,其余的MiddleAsync()将在TPL上进行调度以再次执行,并打印出“I”。 这完成了MiddleAsync()最初返回的任务。

一旦任务被标记为完成,其余的OuterAsync()将在TPL上进行调度以再次执行,并打印出“J”。 这样就完成了OuterAsync()最初返回的任务。

由于OuterAsync()的任务现在完成,Wait()调用返回,Main()打印出“K”。

因此,即使顺序中有一点并行性,C#5异步仍然保证控制台写入按照确切的顺序发生。

让我知道这是否仍然令人困惑:)

您在控制台应用程序中,因此您没有专门的SynchronizationContext ,这意味着您的延续将在线程池线程上运行。

此外,您还没有等待MainTestAsync()的调用。 这意味着当您执行此行时:

 await DoSomething(); 

TestAsync方法将控制返回给Main ,它只是继续正常执行 – 即输出“Ready!” 并等待按键。

同时,在DoSomething完成后的第二秒, TestAsyncawait将继续在线程池线程上并输出“Finished”。

正如其他人所说,控制台程序使用默认的SynchronizationContext ,因此await创建的continuation被调度到线程池。

您可以使用我的Nito.AsyncEx库中的 AsyncContext来提供简单的异步上下文:

 static void Main(string[] args) { Nito.AsyncEx.AsyncContext.Run(TestAsync); Console.WriteLine("Ready!"); Console.ReadKey(); } 

另请参阅此相关问题 。