MSDN文档似乎表明async
和await
适用于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
}
这是async
和await
的合理使用,还是我过度使用?
答案 0 :(得分:38)
已经有两个好的答案了,但要添加我的0.02 ......
如果您正在讨论使用异步操作,async
/ await
可以很好地处理I / O绑定和CPU绑定。
我认为MSDN文档确实对生成异步操作略有倾向,在这种情况下,您确实希望使用TaskCompletionSource
(或类似)来进行I / O绑定和{ {1}}(或类似的)用于CPU绑定。在您创建初始Task.Run
包装后,Task
和async
最好消费。
对于您的特定示例,它实际上取决于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关于此主题的video和MSDN article。他有大量有用的信息。
答案 1 :(得分:18)
await
任何异步操作(即由Task
表示)都是合适的。
关键是,对于IO操作,只要有可能,您希望使用提供的方法,该方法非常核心,是异步的,而不是在阻塞同步方法上使用Task.Run
。如果你在执行IO时阻塞了一个线程(甚至一个线程池线程),你就不会利用await
模型的实际功能。
创建代表您的操作的Task
后,您不再关心它是CPU还是IO绑定。对于调用者来说,它只是一些异步操作,需要await
- 编辑。
答案 2 :(得分:18)
有几件事需要考虑:
Task.Run()
将CPU绑定操作卸载到另一个线程可能是个好主意。虽然代码的用户可以自己做,但如果他们想要的话。Task.Run()
而不是直接运行代码会有一些开销,但如果操作实际需要一些时间,那么它应该不重要。 (此外,返回同步上下文会有一些开销,这是您在库代码中对大多数ConfigureAwait(false)
使用await
的另一个原因。)加权,我认为使用await Task.Run()
是正确的选择。它确实有一些开销,但也有一些优点,这可能很重要。