【问题标题】:How do async and await replace existing approaches?async 和 await 如何替代现有方法?
【发布时间】:2017-06-13 19:55:23
【问题描述】:

来自https://msdn.microsoft.com/en-us/library/mt674882.aspx#Threads

基于异步的异步编程方法优于 几乎所有情况下的现有方法。特别是这种方法 在 IO 绑定操作方面优于 BackgroundWorker,因为 代码更简单,您不必防范竞争条件。 结合Task.Run,​​异步编程优于 BackgroundWorker 用于 CPU 密集型操作,因为异步编程 将运行代码的协调细节与工作分开 Task.Run 转移到线程池。

在我看来,async 函数链最终必须以等待程序无法控制的事情发生而告终。一些 Internet 下载或用户输入等。

如果您的程序必须执行一些冗长的计算,情况会怎样?它必须在一个本身不使用await 的方法中,因为当它自己完成所有工作时,没有什么可等待的。如果不使用await,那么控制就不会返回到调用函数,对吗?如果是这样,那么它肯定根本就不是异步的。

似乎BackgroundWorker 非常适合进行冗长的计算:https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx#Examples

有没有办法使用async/await 来达到这个目的?

【问题讨论】:

  • BackgroundWorker 使用可以转换为几个或单个Task,然后您可以等待它们。
  • 我开始写答案,但意识到答案对于论坛来说太长太宽泛了。但简而言之:异步编程的概念可能有点难以理解。在许多情况下,人们可以从异步编程中受益,但不是全部。 GUI 是一个完美的例子,其中异步编程工作得非常好。国际象棋,没那么多。我建议您阅读有关该主题的更多文献以更深入地了解。
  • 没有等待的异步实际上只是同步
  • Sani,我每天都在阅读更多的文学作品,但我仍然没有看到这个问题得到解决。为什么异步编程不适用于国际象棋?

标签: c# asynchronous async-await backgroundworker


【解决方案1】:

关键在这里:

结合Task.Run,异步编程优于 BackgroundWorker 用于 CPU 密集型操作,因为异步编程 将运行代码的协调细节与工作分开 Task.Run 转移到线程池。

对于 CPU-bound 工作(或 IO-bound,没关系),Task.Run 将在与线程池不同的线程中运行您的代码,因此当前代码可以await 它,因为它正在运行在不同的线程上。从第二个线程的角度来看,工作是同步的,但从第一个线程的角度来看,工作是异步的。

第二件事是协调上下文。我没有用过BackgroundWorker,但是从我对文字的理解来看,它需要你手动检查工作是否完成,然后检索结果,或者可能传播异常等等。在async/await 方法中,所有这些都为您提供。

总而言之,与BackgroundWorker 方式相比,async/await 方式似乎是一种更友好易读的做事方式。

编辑:

您不能在 non-awaitable 方法上使用 await。 CPU 密集型任务永远不可能真正异步,因为需要计算一些东西,并且需要一个线程。要么当前线程进行计算(阻塞),要么将其交给另一个后台线程,因此当前线程不是阻塞和异步的。

如果您熟悉 Node.js,您会注意到处理 CPU 密集型任务非常棘手,而且您经常需要重构代码。

但是,作为用户,您当前的线程是异步的,无论您等待的方法是真正异步的还是使用另一个线程。

【讨论】:

  • 那么解决办法就是正常创建一个任务并等待它,而不是只等待async函数返回的任务?在这里,我正在考虑使用 async 而不使用 await,但我应该考虑使用 await 而不使用 async
  • 是的,如果你想在后台运行一些任务,那么只需使用Task.Run。使用 asyc 而不使用 await 可以有效地使其同步,并且不能使用没有 async 的 await,因为 await 是一个上下文关键字,除非存在 async 修饰符,否则它被视为标识符。
  • 好吧,我说await 没有async 的意思不是我会在非异步函数中使用await 关键字,而是我会等待一些没有它本身不是来自异步函数。
【解决方案2】:

在我看来,一连串的异步函数最终必须以等待程序无法控制的事情发生而告终。

是的;纯 async 代码通常与 I/O 或其他“事件”(例如,计时器、通知或 - 不太常见 - 用户输入)一起使用。

当你的程序必须执行一些冗长的计算时会怎样?

这不是async 的主要用例。但是,它是并行计算 (Parallel / PLINQ) 的绝佳用例,或者如果工作量较小,则使用线程池 (Task.Run)。

如果不使用等待,那么控制就不会返回到调用函数,对吗?如果是这样,那么它肯定根本就不是异步的。

没错;没有awaitasync 方法实际上会同步运行。事实上,如果你在 Visual Studio 中键入它,编译器会给你一个警告,实际上是说“这个方法将同步运行”。因为大多数时候,这是一个错误。

如果您想在线程池线程上运行受 CPU 限制的代码,请使用Task.Run。您可以(并且通常应该)awaitTask.Run 返回的任务,这允许调用者将线程池工作视为异步的(即使它实际上只是在后台线程上同步运行):

await Task.Run(() => ...);

如果您想要并行处理的全部功能,您可以将其封装在 Task.Run 中:

await Task.Run(() => Parallel.ForEach(...));

BackgroundWorker 似乎非常适合冗长的计算

不是真的。检索结果有点尴尬,因为很容易遗漏错误,而且结果不是类型安全的。此外,协调多个 BGW 很快变得困难。我有一个博客系列,shows how BackgroundWorker should essentially be considered obsoleted by Task.Run

【讨论】:

  • @Kyle - Stephen 的There is no thread 帖子也是一个很好的阅读方式:“在我看来,一系列异步函数最终必须以等待程序之外的事情发生而告终控制”,因为掌握它为 I/O 绑定操作带来的好处很重要。
  • 嗯,当我说 BackgroundWorker 非常适合时,我只是说文档使它看起来像是 BackgroundWorker 的用途,而 async/await 显然不是设计时考虑了冗长的计算。
猜你喜欢
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 2020-07-14
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 2016-05-13
  • 2019-09-22
相关资源
最近更新 更多