【问题标题】:Why use C# async/await for CPU-bound tasks [closed]为什么将 C# async/await 用于 CPU 密集型任务 [关闭]
【发布时间】:2018-08-02 09:04:26
【问题描述】:

我正在了解 C# 中的 async/await 关键字,以及它们如何促进异步编程 - 允许线程在其他地方使用,同时一些 I/O 绑定任务(如 db 调用)正在进行。

我多次读到 async/await 用于 I/O 绑定任务,而不是 CPU 绑定任务。 CPU 密集型任务应在单独的后台线程上执行。在这些videos 中多次提到。没事。

但是,当使用Task.Run 在新线程上开始长时间运行的 CPU 密集型工作时,您必须在某个时候使用await。那么我们不是也在这里使用 async/await 来处理 CPU 密集型任务吗?请参见下面的示例。

public async Task SomeMethodAsync()
{
    int result = await Task.Run(() =>
    {
        // Do lots of CPU bound calculations...

        return result;
    }

    // Then do something with the result.
}

【问题讨论】:

  • 关键是 I/O 操作在操作系统的核心是异步的,然后当你调用内核时它无论如何都会异步执行,但是对于 CPU 操作,你需要利用线程来指示 CPU 执行它异步...
  • 您不必在新线程上执行 Task.Run。如果您 await 和 ConfigureAwait(false) 您将(很可能)在不同的线程池线程上运行该代码。而且,您是否暗示您不等待 I/O 操作?除非您真的确定只需要“即发即弃”,否则您应该始终等待。
  • “我读过很多次 async/await 是针对 I/O 密集型任务,而不是 CPU 密集型任务。” 需要引用。我怀疑它是这样写的,你可能会误解这个信息。
  • 这里有解释docs.microsoft.com/en-us/dotnet/standard/async-in-depth。我认为令人困惑的是,对于 CPU 密集型任务,您需要从另一个线程执行它,但您仍然使用 await...
  • 对于桌面 GUI 应用程序,CPU 绑定操作可能会阻塞事件处理程序中的 UI,因此通常最好使处理程序异步并将底层操作包装在 Task.Run 中并等待。使用 ASP.NET,请求已经有一个专用线程。等待 CPU 绑定操作的 Task.Run 只会让您有两个线程在做同样的工作。即使现有请求正在处理,无论是否异步,下一个请求仍会从池中选择另一个线程。对于 I/O 绑定请求,您总是希望利用可用的异步操作,并且不会创建额外的线程。

标签: c# multithreading asynchronous async-await task-parallel-library


【解决方案1】:

如果您await 某事,您有一个承诺,您将从等待的操作中获得结果。通常,您期望等待的操作是异步\后台操作,它将在将来执行。与此同时,您可以继续您的工作,这些工作可以独立于该操作的结果来完成。

async/await 确实是编译器帮助存档。 await 将控制权返回给调用者并异步处理等待的操作。可以以不同的方式提供这种异步性。

I/O 操作一旦进入内核空间就会被异步处理,因此您无需为 I/O 操作创建额外的线程。

在系统 API 调用之后,请求现在在内核空间中,使得 它通往操作系统的网络子系统(例如 /net 在 Linux 内核)。在这里,操作系统将处理网络请求 异步。详细信息可能会有所不同,具体取决于所使用的操作系统( 设备驱动程序调用可能被安排为发送回 运行时,或者可以进行设备驱动程序调用,然后发送信号 返回),但最终会通知运行时网络 请求正在进行中。此时,设备驱动程序的工作 将被安排、正在进行或已经完成( 请求已经“通过网络”发出) - 但因为这就是全部 异步发生,设备驱动程序能够立即 处理其他事情!

另一方面,对于 CPU 操作,您必须以某种方式指示 CPU 异步执行这些操作,通常这是从不同的线程完成的(您拥有 Task.Run 的原因)。

CPU 绑定的异步代码与 I/O 绑定的异步代码有点不同。 因为工作是在 CPU 上完成的,所以没有办法绕过 将一个线程专用于计算。 async 和 await 的使用 为您提供了一种与后台线程交互的干净方式,并且 保持异步方法的调用者响应。请注意,这确实 不对共享数据提供任何保护。如果您使用共享 数据,您仍然需要应用适当的同步 战略。

关键是,在这两种情况下,您都必须await 才能等待承诺兑现。

【讨论】:

    【解决方案2】:

    async/await 是进行 async 编程的现代且最简单的方法,无论作业是否受 I/O 限制; Task 是否需要线程。

    例如,它非常适合 WinForms 或 WPF,因为主线程可以 await 一个子任务来计算小猪小姐一年吃午饭的次数(比方说,这是一个相当长且复杂的 CPU 密集型操作)完成后立即执行下面的下一行。与经典的异步回调不同,它使您的代码如此直观; WaitOnes 或其他机制以及随之而来的杂耍行为。

    MSDN:

    您可以通过使用异步编程来避免性能瓶颈并提高应用程序的整体响应能力。但是,用于编写异步应用程序的传统技术可能复杂,使其难以编写、调试和维护。

    Visual Studio 2012 引入了一种简化的方法,即异步编程,它利用 .NET Framework 4.5 和 Windows 运行时中的异步支持。 编译器完成了开发人员过去所做的艰巨工作您的应用程序保留了类似于同步代码的逻辑结构More...

    操作:

    我读过很多次 async/await 用于 I/O 绑定任务

    不正确。 async/await 是编译器完成更多工作的异步编程的简写。它不仅适用于 I/O 密集型任务。 CPU 密集型任务通常使用线程池线程。

    CPU 密集型任务应在单独的后台线程上执行。

    是的......但这并不意味着你不能await Task。与 I/O 密集型任务不同,CPU 密集型任务需要一个线程来操作,根据定义,需要一个工作线程,因此将从可用线程池中获取一个。

    但是,当使用 Task.Run 在新线程上开始长时间运行的 CPU 密集型工作时,您必须在某个时候等待它

    您不必awaitTask,此类任务称为“即发即弃”。 await 实际上也没有启动它,任务是“热的”。但是,如果不等待,您将面临应用程序退出时任务未完成的风险。例如一个控制台应用程序触发 Task 并且不等待它然后退出。

    那么我们不是也在这里使用 async/await 来处理 CPU 密集型任务吗?

    没错,您可以将它用于任何Task,无论它是否受 I/O 限制。

    更多

    【讨论】:

      猜你喜欢
      • 2011-06-15
      • 1970-01-01
      • 2013-02-26
      • 2021-01-02
      • 1970-01-01
      • 2019-05-29
      • 2021-08-28
      • 2017-04-10
      • 2019-01-02
      相关资源
      最近更新 更多