asynchronous/等待适合IO和CPU绑定的方法吗?

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

我正在执行一个应用程序,执行HTTP请求来检索HTML文档,然后parsing。 我有一个像这样的方法:

 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 ,或者我过度使用它?

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

如果您正在讨论使用asynchronous操作,则async / await对于I / O绑定和CPU绑定都非常有用。

我认为MSDN文档确实对生成asynchronous操作有轻微的倾向,在这种情况下,您希望为I / O绑定和Task.Run (或类似的)使用Task.Run (或类似的)进行CPU绑定。 一旦创build了最初的Task包装器,最好由async 消耗await

对于您的特定示例,这实际上归结为LoadHtmlDocument将花费多less时间。 如果你删除了Task.Run ,你会在调用LoadPage的同一个上下文中执行它(可能在UI线程上)。 Windows 8准则规定,大于50ms的任何操作都应该进行async …请记住,开发者机器上的50ms可能会在客户机上更长时间…

所以如果你能保证LoadHtmlDocument运行时间less于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的video和关于这个主题的MSDN文章 。 他有很多有用的信息。

有几件事情需要考虑:

  • 在GUI应用程序中,您需要尽可能less的代码在UI线程上执行。 在这种情况下,使用Task.Run()将CPU绑定操作卸载到另一个线程可能是一个好主意。 虽然你的代码的用户可以自己做,如果他们想要的。
  • 在ASP.NET应用程序中,没有UI线程,所有你关心的就是性能。 在这种情况下,使用Task.Run()会有一些开销,而不是直接运行代码,但是如果操作实际上需要一些时间,它应该不会很重要。 (另外,返回同步上下文还有一些开销,这也是为什么您应该使用ConfigureAwait(false)来实现库代码中的大部分await的一个原因。
  • 如果你的方法是asynchronous的(BTW也应该反映在方法的名字中,而不仅仅是它的返回types),人们会期望它不会阻塞同步上下文线程,即使CPU限制工作。

加权,我认为使用await Task.Run()在这里是正确的select。 这确实有一些开销,但也有一些优势,这可能是重要的。

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

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

一旦你创build了一个表示你的操作的Task ,你不再在乎它是CPU还是IO绑定。 对于调用者来说,这只是一些需要awaitasynchronous操作。