【问题标题】:Why use Async/await all the way down为什么要一直使用 Async/await
【发布时间】:2015-06-30 18:44:34
【问题描述】:

我想澄清一下一直使用 Await 和 Async 的额外好处。

如果我的应用程序正在调用 await Func1()(因此这里不会阻塞 UI)。而Func1 正在调用await Func2(),但是Func2() 的结果对于Func1 完成它的工作很重要,那我为什么需要让Func2() 等待。 Func1() 执行将花费同样长的时间,因为它正在等待 Func2 完成。 await 在这里所做的就是添加 StateMachine 开销。

我在这里遗漏了什么吗?

【问题讨论】:

  • 如果Func2() 正在等待外部资源:保持线程没有任何好处...

标签: c# .net asynchronous async-await


【解决方案1】:

主要好处是等待异步方法将工作线程返回到池中以用于其他调用(例如,对您的 .NET MVC Web 应用程序的 Web 请求)。异步工作在 IO 完成线程上完成。当等待的方法完成时,另一个工作线程将收集结果并恢复执行。这可以防止工作线程池耗尽,并允许您的应用程序处理更多负载(取决于 CPU、内存和网络吞吐量)。

至于“一直等待”,这对我来说似乎是个问题。通常await 与您的应用程序必须等待的外部资源(数据库调用、HTTP 请求等)相关联。如果您等待没有外部 IO 依赖项的代码,您将创建不需要的开销。在async 方法链中可以有多个awaits,但是等待一些本身调用await 但没有其他外部IO 依赖的代码是不好的,只会增加回调/编译器开销。

【讨论】:

    【解决方案2】:

    在基于 UI 的应用程序中,async await 提供了一种非常简洁的方式来处理导致 UI 更新的异步调用。如果 UI 中的“顶级”处理程序正在等待结果,那么除非这样做有意义,否则在整个链条中都可能没有真正的好处。 async await 的设计目标是使异步编程看起来更加同步和连续,并且没有分散的回调等 - 使异步编码更易于评估。这不是你需要在任何地方随意使用的东西。

    【讨论】:

      【解决方案3】:

      一个更好的口号async一直向上。因为你从一个异步操作开始,让它的调用者异步,然后是下一个调用者等等。

      当您有一个固有的异步操作(通常是 I/O,但不一定)并且您不想浪费线程等待操作完成时,您应该使用 async-await。选择async 操作而不是同步操作不会加快操作速度。这将花费相同的时间(甚至更多)。它只是使该线程能够继续执行其他一些 CPU 密集型工作,而不是浪费资源。

      但为了能够await 进行该操作,该方法必须是async ,调用者需要await 等等。

      所以async 一直向上使您能够实际进行异步调用并释放任何线程。如果一直不是async,那么某个线程被阻塞了。

      所以,这个:

      async Task FooAsync()
      {
          await Func1();
          // do other stuff
      }
      
      async Task Func1()
      {
          await Func2();
          // do other stuff
      }
      
      async Task Func2()
      {
          await tcpClient.SendAsync();
          // do other stuff
      }
      

      比这更好:

      void Foo()
      {
          Func1();
          // do other stuff
      }
      
      void Func1()
      {
          Func2().Wait();  // Synchronously blocking a thread.
          // do other stuff
      }
      
      async Task Func2()
      {
          await tcpClient.SendAsync();
          // do other stuff
      }
      

      【讨论】:

      • 所以除非你一直等待,否则没有任何好处。
      • 所以,我尝试了Func2().Wait(); 并且执行冻结了(尽管async-await 没问题)。我不确定我是否遵循它,但有其他选择吗?我的意思是,如果我要等待,为什么还要让它异步呢?
      【解决方案4】:

      如果您不等待异步方法,“此异步方法缺少 'await' 运算符并将同步运行”会发生什么情况。要利用异步方法的好处,您必须等待它,将调用者转换为可以等待的异步方法等。

      从流程图中,您可以看到异步方法返回一个任务(承诺将在未来完成的工作)并将控制权交给它的调用者。在此期间,调用者可以继续工作,而不依赖于这个结果。显然,这需要在调用堆栈中冒泡,以找到所有可以在没有结果的情况下完成的有益工作(在 UI 应用程序中,这项工作将包括解除对 UI 的阻塞,这就是为什么它一直到事件处理程序都是异步的在下面的例子中)。


      从我最初的误读。所以你找到了一些你需要调用的异步代码,是否值得 async await 模式传播你的代码:

      我听说过很多关于 async-await 的主要问题是它的语法太简单了,无法实现它的实际功能。 Program flow 变得复杂。我真的很喜欢 async-await,但不幸的是,在我见过的大多数异步代码中,它并不值得,而且只会不必要地破坏我的调用堆栈。

      要记住的一件好事是50ms rule

      “这是 Microsoft 在 WinRT API 中遵循的规则;任何花费少于 50 毫秒的东西都被认为是“快的”并且足够接近“立即”,它们不需要异步 API。”

      这是在鼓励异步等待的情况下使用的。但是,我认为它同样应该应用于告诉开发人员使用 async-await 来实现基本即时功能将其删除。


      【讨论】:

      【解决方案5】:

      通常,async/await 背后的推理反过来:

      无论出于何种原因,您认为使用await 编写Func2 会更容易。因此,您可以通过添加所需的awaits 来简化方法,这意味着您还必须更改方法签名(它现在将包括async 关键字和TaskTask<T> 的返回类型)。

      由于返回类型发生了变化,你不能再像以前(var result = Func2();)那样调用Func2,所以现在需要更改调用方法Func1。适应Func1 的最简单方法通常是将其设置为asyncawait Func2()

      然后,出于同样的原因(更改签名),您将需要更改对Func1 的所有调用,依此类推,直到您到达某种入口点(UI 事件处理程序或您的Main方法,或其他)。

      因此,您不要开始创建“最外层”方法async 并继续执行“内部”(称为)方法;您通常会在相反的方向(从“最里面”的方法回到调用的方法)做事情asyncThis other answer 将此想法称为“一直异步”

      【讨论】:

      • 所以它就像癌症一样。
      • @matao:如果您认为async/await 是一件坏事,那么,也许,是的。我个人认为它比旧的替代异步编程模式产生了更好的代码,所以从我的角度来看,将它与疾病进行比较是没有根据的。当然,首先并不是所有的东西都应该是异步的。在很多情况下,引入异步/并发根本没有多大意义,因此async/await 本身不会传播到任何地方。
      • (太长了,不得不拆开)[1] 我当然有点夸大其词了,但在我看来,如果你不小心,那么你很容易就会得到每一个在你的代码库中调用异步/等待,特别是如果你的初级开发人员不了解它的用途以及何时不使用它。
      • @matao:我认为评论部分没有足够的空间来讨论async/await的一般优点和缺点,也许聊天会更合适。简单地说:(1) 我怀疑async/await 主要是为了帮助程序员创建响应式用户界面而引入的。在早期的 .NET 中,您必须进行显式的多线程和回调,这并不容易。很多人都犯了这个错误(即在更新 UI 控件之前没有返回 UI 线程)。 async/await这个场景变得容易多了。但实际上它并没有涵盖所有多线程场景,例如您的 [3]。
      • (2) 鉴于您的 [1] 和 esp。 [2],如果您真的不想关心某些东西是同步实现还是异步实现,那么对我来说合乎逻辑的结论是通过使 everything 表面上的异步来隐藏这个实现细节。这对于ValueTask<T> 是可行的,它是struct(与Task<T> 不同)足够轻量级,在实际上完全阻塞/同步的方法的情况下不会产生太多开销。但是,是的,设计一个 100% 异步的 API(即使在没有必要的时候)将是矫枉过正的恕我直言。
      猜你喜欢
      • 2012-08-27
      • 2017-10-26
      • 2018-04-11
      • 1970-01-01
      • 2019-02-26
      • 1970-01-01
      • 2020-01-24
      • 1970-01-01
      相关资源
      最近更新 更多