我正在尝试了解 C# 中异步代码的控制流。
我理解这样的概念:当异步方法遇到“await”时,它将控制权交还给调用它的方法,允许线程继续工作而不是被阻塞。
我不明白的是线程什么时候返回到包含“await”的方法并完成该方法?流程是在编译时决定的,还是在运行时完成的,其中线程的流程由先完成的等待方法决定?
当我试图研究答案时,我看到的只是“这与状态机有关,它取决于编译器,而且解释起来太复杂了”。
我有两个示例代码片段,我想了解它们的流程以及日志的顺序。
public async Task<ActionResult> Main()
{
var task1 = SomeMethod("1");
var task2 = SomeMethod("2");
var task3 = SomeMethod("3");
var task4 = SomeMethod("4");
await task2;
await task3;
await task4;
await task1;
return Ok();
}
private async Task SomeMethod(string input)
{
await Task.Delay(rnd.Next(1, 200));
Console.WriteLine(input);
}
private async Task Main()
{
Console.WriteLine("1");
var result = Method1();
Console.WriteLine("2");
await result;
Console.WriteLine("3");
}
private async Task Method1()
{
Console.WriteLine("4");
var result = Method2();
Console.WriteLine("5");
await result;
}
private async Task Method2()
{
Console.WriteLine("6");
var result = Method3();
Console.WriteLine("7");
await Task.Delay(300);
await result;
}
private async Task Method3()
{
Console.WriteLine("8");
await Task.Delay(300);
Console.WriteLine("9");
}
我不明白的是线程什么时候返回到包含“await”的方法并完成该方法?
实际上并非如此。
async
方法不依赖于线程。我在我的 async
和 await
博客文章 中描述了它们的工作原理,试图避免陷入状态机细节的混乱,但也为您提供了您需要了解的一切。具体来说:
await
将默认捕获上下文。这个“上下文”通常是SynchronizationContext.Current
;在控制台应用程序中,通常没有 SynchronizationContext
,因此使用线程池上下文。async
方法在 await
之后恢复时,它将在该上下文中恢复。在线程池上下文的情况下,延续只是被调度到线程池上。现在,在其他情况(尤其是 UI 应用程序)中,存在
SynchronizationContext
并且 SynchronizationContext
is 绑定到特定线程(UI 线程)。但这里的情况并非如此。
您的第一个代码示例同时运行 4 个异步方法。它们中的每个都将继续在线程池线程上运行并从那里写入其输出。结果是随机顺序,但始终会写入全部 4 个。
您的第二个代码示例将导致:
1
4
6
8
7
5
2
{after 300ms}:
9
3
async
介绍帖子中进行了描述):
Main
打印 1
并调用 Method1
。Method1
打印 4
并调用 Method2
。Method2
打印 6
并调用 Method3
。Method3
打印 8
并调用 Task.Delay
。 Task.Delay
返回一个将在 300 毫秒后完成的任务。Method3
await
是从Task.Delay
返回的任务。这会导致 Method3
将未完成的任务返回给 Method2
。Method2
打印 7
并调用 Task.Delay
。 Task.Delay
返回一个将在 300 毫秒后完成的任务。Method2
await
是从Task.Delay
返回的任务。这会导致 Method2
将未完成的任务返回给 Method1
。Method1
打印5
并且await
是从Method2
返回的任务。这会导致 Method1
将未完成的任务返回给 Main
。Main
打印2
并且await
是从Method1
返回的任务。大约 300 毫秒后,两个
Task.Delay
任务都完成,并且 Method3
和 Method2
的延续任务被安排到线程池中。这里存在竞争条件,但输出上没有任何可观察到的结果。如果 Method2
首先执行,它只会 await
从 Method3
返回的任务。所以:
Method3
恢复执行(在线程池线程上)。Method3
打印 9
并完成。Method2
恢复执行(在线程池线程上)。Method2
完成。Method1
恢复执行(在线程池线程上)。Method1
完成。Main
恢复执行(在线程池线程上)。Main
打印 3
并完成。