在“Ready!”之后看到“Finished”的原因 是因为与异步方法有一个常见的混淆点,与 SynchronizationContexts 无关。SynchronizationContext 控制在哪个线程上运行,但“异步”有其自己非常具体的关于排序的规则。否则程序会发疯!:)
'await' 确保当前异步方法中的其余代码在等待的事情完成之前不会执行。它对调用者没有任何承诺。
您的异步方法返回“void”,它适用于不允许原始调用者依赖方法完成的异步方法。如果您希望您的调用者也等待,您需要确保您的异步方法返回 a 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() 是否完成。它立即返回一个并行启动的任务。
由于并行性,在 InnerAsync() 完成对 DoSomething() 返回的任务的完整性测试与实际启动的 DoSomething() 任务之间存在竞争。
一旦 DoSomething() 启动,它会打印出“F”,然后休眠一秒钟。
同时,除非线程调度非常混乱,否则 InnerAsync() 几乎可以肯定现在已经意识到 DoSomething() 还没有完成。现在异步魔法开始了。
InnerAsync() 将自己从调用堆栈中拉出来,并说它的任务是不完整的。这会导致 MiddleAsync() 将自己从调用堆栈中拉出并说自己的任务不完整。这会导致 OuterAsync() 将自己从调用堆栈中拉出,并说它的任务也不完整。
任务返回到 Main(),它注意到它不完整,然后 Wait() 调用开始。
同时...
在该并行线程上,在 DoSomething() 中创建的旧式 TPL 任务最终完成休眠。它打印出“G”。
一旦该任务被标记为完成,InnerAsync() 的其余部分就会被安排在 TPL 上再次执行,并打印出“H”。这样就完成了最初由 InnerAsync() 返回的任务。
一旦该任务被标记为完成,MiddleAsync() 的其余部分就会被安排在 TPL 上再次执行,并打印出“I”。这样就完成了最初由 MiddleAsync() 返回的任务。
一旦该任务被标记为完成,OuterAsync() 的其余部分就会被安排在 TPL 上再次执行,并打印出“J”。这样就完成了最初由 OuterAsync() 返回的任务。
由于 OuterAsync() 的任务现已完成,Wait() 调用返回,Main() 打印出“K”。
因此,即使在顺序上有一点并行性,C# 5 async 仍然保证控制台写入以该确切顺序发生。
让我知道这是否仍然令人困惑:)