43

MSDN 文档似乎声明asyncawait适用于 IO-bound 任务,而Task.Run应该用于 CPU-bound 任务。

我正在开发一个应用程序,该应用程序执行 HTTP 请求以检索 HTML 文档,然后对其进行解析。我有一个看起来像这样的方法:

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync())
        return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound
}

这是对 and 的好和合适的使用asyncawait还是我过度使用它?

4

3 回答 3

44

已经有两个很好的答案,但是要添加我的 0.02 ......

如果您正在谈论使用异步操作,那么async/await对 I/O 密集型和 CPU 密集型都非常有效。

我认为 MSDN 文档确实略微倾向于生成异步操作,在这种情况下,您确实希望将TaskCompletionSource(或类似的)用于 I/O-bound 和Task.Run(或类似的)用于 CPU-bound。创建初始Task包装器后,最好由and使用它。asyncawait

对于您的特定示例,这实际上取决于需要多少时间LoadHtmlDocument。如果删除Task.Run,您将在调用的同一上下文中执行它LoadPage(可能在 UI 线程上)。Windows 8 指导方针规定,任何超过 50 毫秒的操作都应进行async......请记住,您的开发人员机器上的 50 毫秒可能在客户端机器上更长......

所以如果你能保证LoadHtmlDocument运行时间少于 50ms,你可以直接执行它:

public async Task<HtmlDocument> LoadPage(Uri address)
{
  using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
  using (var responseContent = httpResponse.Content)
  using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound
    return LoadHtmlDocument(contentStream); //CPU-bound
}

但是,我建议ConfigureAwait@svick 提到:

public async Task<HtmlDocument> LoadPage(Uri address)
{
  using (var httpResponse = await new HttpClient().GetAsync(address)
      .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
  using (var responseContent = httpResponse.Content)
  using (var contentStream = await responseContent.ReadAsStreamAsync()
      .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
    return LoadHtmlDocument(contentStream); //CPU-bound
}

使用ConfigureAwait,如果 HTTP 请求没有立即(同步)完成,那么这将(在这种情况下)导致LoadHtmlDocument在线程池线程上执行而无需显式调用Task.Run.

如果您对async这个级别的性能感兴趣,您应该查看 Stephen Toub 的视频MSDN 文章关于该主题。他有大量有用的信息。

于 2013-02-15T20:20:46.957 回答
21

它适用于await任何异步操作(即由 a 表示Task)。

关键是对于 IO 操作,只要有可能,您都希望使用提供的方法,即,在它的核心,异步,而不是使用Task.Run阻塞同步方法。如果您在执行 IO 时阻塞了一个线程(甚至是线程池线程),那么您就没有利用await模型的真正力量。

一旦您创建了一个Task代表您的操作的操作,您就不再关心它是否受 CPU 或 IO 限制。对于调用者来说,这只是一些需要await-ed 的异步操作。

于 2013-02-15T15:39:03.083 回答
20

有几件事情需要考虑:

  • 在 GUI 应用程序中,您希望在 UI 线程上执行尽可能少的代码。在这种情况下,将 CPU 绑定操作卸载到另一个线程使用Task.Run()可能是一个好主意。尽管您的代码的用户可以自己执行此操作,如果他们愿意的话。
  • 在像 ASP.NET 应用程序中,没有 UI 线程,您只关心性能。在这种情况下,使用Task.Run()而不是直接运行代码会有一些开销,但如果操作实际上需要一些时间,这应该不会很重要。(此外,返回同步上下文会产生一些开销,这也是为什么您应该在库代码中使用ConfigureAwait(false)大多数s 的另一个原因。)await
  • 如果你的方法是异步的(顺便说一句,它也应该反映在方法的名称中,而不仅仅是它的返回类型),人们会期望它不会阻塞同步上下文线程,即使对于 CPU 密集型工作也是如此。

考虑到这一点,我认为await Task.Run()在这里使用是正确的选择。它确实有一些开销,但也有一些优势,这可能很重要。

于 2013-02-15T16:03:33.437 回答