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

时间:2013-02-15 14:29:42

标签: 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
}

这是asyncawait的合理使用,还是我过度使用?

3 个答案:

答案 0 :(得分:38)

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

如果您正在讨论使用异步操作,async / await可以很好地处理I / O绑定和CPU绑定。

我认为MSDN文档确实对生成异步操作略有倾向,在这种情况下,您确实希望使用TaskCompletionSource(或类似)来进行I / O绑定和{ {1}}(或类似的)用于CPU绑定。在您创建初始Task.Run包装后,Taskasync最好消费

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

因此,如果您能保证async的运行时间不会超过50毫秒,您可以直接执行它:

LoadHtmlDocument

但是,我建议{@ 1}} @svick提到:

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,如果HTTP请求没有立即(同步)完成,那么这将(在这种情况下)导致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

如果您对此级别的LoadHtmlDocument表现感兴趣,请查看Stephen Toub关于此主题的videoMSDN article。他有大量有用的信息。

答案 1 :(得分:18)

await任何异步操作(即由Task表示)都是合适的。

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

创建代表您的操作的Task后,您不再关心它是CPU还是IO绑定。对于调用者来说,它只是一些异步操作,需要await - 编辑。

答案 2 :(得分:18)

有几件事需要考虑:

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

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