isync/await是否适合IO和CPU绑定的方法?

Sam*_*Sam 40 c# asynchronous async-await c#-5.0

MSDN文档看起来指出asyncawait适合于IO密集型任务,而Task.Run应该用于CPU密集型任务.

我正在处理一个执行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
}
Run Code Online (Sandbox Code Playgroud)

这是好的和适当的使用asyncawait,或者我是否过度使用它?

Ste*_*ary 40

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

如果您正在讨论使用异步操作,那么async/ await对I/O绑定和CPU绑定都很有效.

我认为MSDN文档确实对生成异步操作略有倾向,在这种情况下,您确实希望使用TaskCompletionSource(或类似)I/O绑定和Task.Run(或类似)CPU绑定.一旦你创建了最初的Task包装,它是最好的消耗asyncawait.

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

因此,如果您可以保证LoadHtmlDocument将运行少于50毫秒,您可以直接执行它:

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
}
Run Code Online (Sandbox Code Playgroud)

但是,我建议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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @mmcrae:您的理解是正确的。当 LoadPage 恢复时,它在线程池线程上运行,然后执行 LoadHtmlDocument(在该线程池线程上)。 (2认同)

Ser*_*rvy 19

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

关键点在于,对于IO操作,只要有可能,您希望使用提供的方法,即非常核心的异步方法,而不是使用Task.Run阻塞同步方法.如果在执行IO时阻塞线程(甚至线程池线程),则不会充分利用await模型的实际功能.

一旦创建了Task代表您的操作,就不再关心它是CPU还是IO绑定.对于调用者来说,它只是需要进行的一些异步操作await.


svi*_*ick 18

有几件事需要考虑:

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

加权,我认为使用await Task.Run()是正确的选择.它确实有一些开销,但也有一些优点,这可能是重要的.