使用ConfigureAwait(false)和Task.Run有什么区别?

61

我了解在库代码中使用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
        });
}

这两种方法之间有什么区别?

4个回答

76
当你使用 Task.Run 时,表示你有一些可能需要很长时间的CPU工作,因此它应该始终在线程池线程上运行。
当你使用 ConfigureAwait(false) 时,表示该async方法的其余部分不需要原始上下文。 ConfigureAwait 更像是一个优化提示;它并不总是意味着继续执行在线程池线程上运行。

2
我正在阅读您在http://blog.stephencleary.com/2012/02/async-and-await.html上的文章,但在ConfigureWait(false)周围有些困惑,所以来到这里。您在这里说“异步方法的其余部分不使用原始上下文”,这对我有些澄清。但是,您能否举一个“不需要上下文”的例子,以便我可以巩固我的理解? - rism
6
我的意思是指任何不需要上下文的事物。在这种情况下,“LoadHtmlDocument”正在解析HTML;这个解析过程不必在UI线程上执行,因此它不需要上下文。 - Stephen Cleary
1
@Stephen Cleary,您能否提供一个示例,说明何时需要上下文来完成异步方法的其余部分? - vborutenko
4
@cosset: 当然可以。由于这是一个 UI 应用程序,需要注意的一个例子就是更新 UI 控件。例如,如果异步方法的结尾更新了一个标签或进度条,则需要在 UI 上下文中恢复。如果没有这样做,会导致跨线程异常。 - Stephen Cleary
1
@NStuke:这将是与“LoadPage”开始执行的上下文相同的情境。 - Stephen Cleary
显示剩余10条评论

17
在这种情况下,你的Task.Run版本会有更多的开销,因为第一个await调用(await client.GetAsync(address))仍然会转移到呼叫上下文中,Task.Run调用的结果也是如此。
另一方面,在第一个示例中,你的第一个Async()方法被配置为不需要转移到呼叫上下文中,这使得继续在后台线程上运行。因此,在调用者的上下文中不会有任何转移。

6

同意@Stephen的回答,如果仍有疑惑,请参阅以下屏幕截图: 1# 没有ConfigureAwait(false)
请见下图,主线程尝试更新标签 进入图像描述

2# 使用ConfigureAwait(false)
请见下图,工作线程尝试更新标签 进入图像描述


1
作为旁注,在这两种情况下,LoadPage()仍然可能会阻塞您的UI线程,因为await client.GetAsync(address)需要时间来创建一个任务以传递给ConfigureAwait(false)。而您耗时的操作可能已经在返回任务之前开始了。
一种可能的解决方案是使用这里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
}

我尝试了这个,它起作用了。 我唯一的问题是,每个具有await的线程中,我是否总是需要使用"await new sync....."?还是只需要在我的httpclient类上使用它? - zackmark15

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接