【问题标题】:How do nested calls of async functions work?异步函数的嵌套调用如何工作?
【发布时间】:2017-09-07 22:29:12
【问题描述】:

我是异步编程的新手,我的问题可能看起来很傻。任何人都可以在下面的伪代码示例中解释异步调用是如何工作的吗?

    public async Task MyMethod()
    {
     while(true)
     {
      await Method1();
      //do something in MyMethod 
      await Task.Delay(10000);
     }
    }

    private async Task Method1()
    {
    //do something in Method1 before await
    await Method2();
    //do something in Method1 after await
    }

    private async Task Method2()
    {
    //do something in Method2 before await
    await LongRunMethod();
    }

据我了解,程序是这样工作的

  1. MyMethod() 在无限循环中调用 Method1()
  2. Method1() 运行“在等待之前在 Method1 中做一些事情”
  3. Method1() 启动 Method2() 并将控制权返回给 MyMethod()
  4. MyMethod() 启动 10000 毫秒延迟并将控制权返回给其调用者
  5. Method2() 完成“在等待之前在 Method2 中做某事”,启动 LongRunMethod() 并将控制权返回给 MyMethod1()
  6. Method1() 完成“在等待后在 Method1 中做某事”。之后通过什么方法获得控制权?
  7. Lo​​ngRunMethod() 完成工作
  8. Method2() 完成了它的工作。它是否将控制权返回给 Method1()?
  9. 10000 毫秒后,一切都从步骤 1 开始重复。

我的问题是

  1. 上述步骤顺序是否正确?
  2. 在 Method1() 和 Method2() 完成第 6 步和第 8 步的工作后,哪些方法会获得控制权?
  3. 如果 LongRunMethod() 运行时间超过 10000 毫秒会怎样?

谢谢

【问题讨论】:

  • 你永远不会等待_task。这是自找麻烦。
  • 在 JS 中,chain async 方法而不是 nesting 它们是更好的做法...因为在嵌套时,您会失去控制...但是链接可以确保您的进程始终以相同的顺序运行。 stackoverflow.com/questions/25181984/…
  • 谢谢基里尔,我已经更改了代码。
  • 实际上,这改变了您的问题 3 的答案。以前如果 LongRunMethod 运行时间超过 10 秒,则将并行启动另一个调用,而现在无论多长时间,您都可以很好地顺序执行每种方法都需要。尽管如此,每个Task 都需要在某个时候被await 编辑。
  • 一个更有趣的代码变体应该包括await _task after await Task.Delay(10000)

标签: c# asynchronous


【解决方案1】:

以上步骤顺序正确吗?

没有。

正确的顺序是

  1. MyMethod 调用 Method1。
  2. Method1 在调用 Method2 之前执行代码。
  3. Method1 调用 Method2。
  4. Method2 在调用 LongRunMethod 之前执行代码。
  5. Method2 调用 LongRunMethod。
  6. Lo​​ngRunMethod 将任务返回给 Method2。
  7. Method2 询问任务是否完成。假设不是。
  8. Method2 将“不做额外的工作,然后将 method2 的任务标记为完成”作为刚刚返回的任务的继续,并将其任务返回给 Method1。
  9. Method1 询问刚刚从 method2 返回的任务。让我们假设它不完整。它将 await 之后发生的工作分配为 method1 任务的延续,并将该任务返回给其调用者。
  10. MyMethod 询问刚刚从 Method1 返回的任务。让我们假设它不完整。它签署该工作的其余部分以继续执行 任务,并将该任务返回给其调用者。
  11. 那个调用者,不管它是什么,做它做的任何工作。
  12. 在未来某个时间点,LongRunMethod 的任务会异步完成,并请求在适当的上下文中继续执行。
  13. 最终该上下文开始运行代码,它运行 LongRunMethod 任务的延续,回想一下,不做任何工作,然后将 Method2 任务标记为完成。
  14. 确实如此。 Method2 的任务现已完成,因此它请求继续在适当的上下文中运行。
  15. 最终运行。回想一下,Method2 任务的延续是运行 Method1 的其余部分。它这样做,然后将 Method1 的任务标记为完成。这要求 Method1 的任务继续运行。
  16. 最终继续运行。它的继续是调用Task.Delay。
  17. Task.Delay 返回一个任务。 MyMethod 询问任务。它不完整。因此,它签署了“回到循环顶部”作为该任务的继续。然后返回。
  18. 无论是什么代码触发了 LongRunMethod 任务的继续,都会继续工作。在未来 10 秒后的某个时间点,延迟任务完成并请求继续执行。
  19. 最终继续在适当的上下文中执行。继续是“回到循环的顶部”,整个事情重新开始。

请注意,MyMethod 返回的任务永远不会正常完成;如果这些任务中的任何一个抛出异常,那么它就会异常完成。为简单起见,我在您的示例中忽略了对异常延续的检查,因为没有异常处理。

Method1() 和 Method2() 完成工作后,哪些方法会获得控制权?

Method1 的剩余部分最终在 Method2 返回的任务完成后获得控制权。 MyMethod最终在 Method1 返回的任务完成后获得控制权。何时安排延续的确切细节取决于代码运行的上下文;如果它们在表单的 UI 线程上,这与它们在 Web 服务器上的工作线程中是非常不同的。

如果 LongRunMethod() 运行时间超过 10000 毫秒会发生什么?

我想你的意思是如果 LongRunMethod 返回的任务超过十秒没有完成会发生什么。然后没有什么特别有趣的事情发生。直到 LongRunMethod 的任务完成后才开始延迟。 您等待该任务

我认为你根本不明白“等待”是什么意思。您似乎认为“等待”的意思是“异步启动此代码”,但这根本不是它的意思。假设代码已经是异步的。等待管理异步;它不会创建它。

相反,await是tasks上的操作符,意思是如果这个task完成了就继续;如果此任务未完成,则注册此方法的其余部分作为该任务的继续,并通过返回给调用者来尝试在此线程上找到一些其他工作。

在等待的任务完成之前没有执行等待之后的代码; await 表示异步等待任务完成。这并不意味着“异步启动此代码”。 Await 用于声明异步工作流中的哪些点必须等待任务完成才能继续工作流。这就是为什么它的名字中有“等待”。

【讨论】:

  • 谢谢你,埃里克。这正是我想知道的。现在一切都说得通了。我有一个关于任务的问题。任务完成后会发生什么?例如,在我的理解中,新的 Method1 和 Delay 任务是在每次无限循环迭代中创建的。它们是否在迭代结束时处理?
  • 另一个问题是关于您的注释“MyMethod 返回的任务永远不会正常完成”。你这是什么意思?应该做哪些代码更改才能正确完成 MyMethod 任务?谢谢。
  • @MBK:不客气。任务是引用类型的对象;当没有更多的活动引用并且 GC 运行时,它们会像任何其他对象一样被垃圾收集。 C# 7 允许创建值类型任务,这可以在某些情况下降低收集压力,但代价是与值类型相关联的按值复制成本。我的建议:只有在普通任务出现性能问题时才使用值类型的任务。
  • @MBK:与异步方法关联的任务在该方法执行返回时正常完成; MyMethod 永远不会执行返回,因此它永远不会正常完成。
  • @MBK:同样,在void Caller() {int c = MyLoop();} int MyLoop() { while (true) ;}内,MyLoop不会正常返回,所以c实际上永远不会被赋予MyLoop的值。在具有无限循环的函数上具有返回类型往往会让消费者感到有些困惑。有返回类型意味着函数可以返回。
猜你喜欢
  • 1970-01-01
  • 2018-04-02
  • 2020-07-13
  • 2012-10-12
  • 1970-01-01
  • 2014-10-05
  • 2019-10-02
  • 1970-01-01
相关资源
最近更新 更多