async-await 中的控制流程如何工作?

问题描述 投票:0回答:1

我正在尝试了解 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");
    }
c# asynchronous async-await
1个回答
0
投票

我不明白的是线程什么时候返回到包含“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
    并完成。
© www.soinside.com 2019 - 2024. All rights reserved.