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

我知道build议在库代码中使用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 }); } 

这两种方法有什么区别?

当你说Task.Run ,你说你有一些CPU的工作可能需要很长时间,所以它应该总是在线程池线程上运行。

当你说ConfigureAwait(false) ,你说这个async方法的其余部分不需要原来的上下文。 ConfigureAwait更多的是一个优化提示; 它并不总是意味着继续在线程池线程上运行。

在这种情况下,你的Task.Run版本将会有更多的开销,因为第一个await的调用( await client.GetAsync(address) )仍然会callback到调用上下文中, Task.Run调用的结果也是Task.Run

在第一个示例中,另一方面,您的第一个Async()方法被configuration为不需要封送到调用上下文中,这允许继续在后台线程上继续运行。 因此,在调用者的背景下不会有任何callback。

作为一个侧面说明,在这两种情况下LoadPage() 仍然可以阻止你的UI线程,因为await client.GetAsync(address)需要时间创build一个任务传递给ConfigureAwait(false) 。 而且您的耗时操作可能在任务返回之前已经开始。

一个可能的解决scheme是从这里使用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 } 

同意@Stephen回答,如果还是混淆见下面截图1#不用ConfigureAwait(false)
见下图主线程试图更新标签 在这里输入图像说明

2#With ConfigureAwait(false)
看下面的图像工作线程试图更新标签 在这里输入图像说明