我理解建议在库代码中使用ConfigureAwait(false)
作为await
,以便后续代码不会在调用者的执行上下文中运行,后者可能是一个UI线程。我也理解,出于同样的原因,应该使用await Task.Run(CpuBoundWork)
代替CpuBoundWork()
。
ConfigureAwait
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address).ConfigureAwait(false))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync().ConfigureAwait(false))
return LoadHtmlDocument(contentStream); //CPU-bound
}
Task.Run
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
return await Task.Run(async () =>
{
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
});
}
这两种方法有什么不同?
答案 0 :(得分:68)
当你说Task.Run
时,你说要做一些CPU工作可能需要很长时间,所以应该总是在线程池线程上运行。
当您说ConfigureAwait(false)
时,您说的是async
方法的其余部分不需要原始上下文。 ConfigureAwait
更像是一个优化提示;它不总是意味着继续在线程池线程上运行。
答案 1 :(得分:16)
在这种情况下,您的Task.Run
版本会有更多的开销,因为第一个等待调用(await client.GetAsync(address)
)仍将编组回调用上下文,{{Task.Run
1}}来电。
在第一个示例中,另一方面,您的第一个Async()
方法被配置为不需要编组回调用上下文,这允许继续在后台线程上运行。因此,不会有任何编组回到调用者的上下文中。
答案 2 :(得分:3)
答案 3 :(得分:1)
作为旁注,在这两种情况下LoadPage()
都可以仍然阻止您的UI线程,因为await client.GetAsync(address)
需要时间来创建要传递给ConfigureAwait(false)
的任务。在返回任务之前,您的耗时操作可能已经开始。
一种可能的解决方案是使用here中的SynchronizationContextRemover
:
public async Task<HtmlDocument> LoadPage(Uri address)
{
await new SynchronizationContextRemover();
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
}