【问题标题】:What are the differences between using ConfigureAwait(false) and Task.Run?使用 ConfigureAwait(false) 和 Task.Run 有什么区别?
【发布时间】:2013-02-01 02:24:37
【问题描述】:

我了解建议在库代码中将ConfigureAwait(false) 用于awaits,以便后续代码不会在调用者的执行上下文(可能是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
        });
}

这两种方法有什么区别?

【问题讨论】:

    标签: c# .net .net-4.5 async-await c#-5.0


    【解决方案1】:

    同意@Stephen 的回答,如果仍然感到困惑,请参阅下面的屏幕截图 1# 没有 ConfigureAwait(false)
    见下图主线程试图更新标签

    2# 使用 ConfigureAwait(false)
    请参阅下面的图像工作线程尝试更新标签

    【讨论】:

      【解决方案2】:

      附带说明一下,在这两种情况下,LoadPage() 可能仍会阻塞您的 UI 线程,因为 await client.GetAsync(address) 需要时间来创建任务以传递给 ConfigureAwait(false)。并且您的耗时操作可能在任务返回之前已经开始。

      一种可能的解决方案是使用来自hereSynchronizationContextRemover

      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
      }
      

      【讨论】:

      • 我试过了,它奏效了。我唯一的问题是。我是否总是需要在等待的每个线程中使用等待新同步.....?还是我只在我的 httpclient 类上需要这个?
      【解决方案3】:

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

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

      【讨论】:

      • 我正在blog.stephencleary.com/2012/02/async-and-await.html 阅读您的文章,我对 ConfigureWait(false) 有点困惑,所以到此结束。在这里,您说“异步方法的其余部分不是原始上下文”,这对我来说有些澄清。您能否举一个“不需要的上下文”的示例,以便我巩固理解?
      • @rism:我的意思是任何不需要需要上下文的东西。在这种情况下,LoadHtmlDocument 正在解析 HTML;此解析不必在 UI 线程上完成,因此不需要上下文。
      • @Stephen Cleary,当异步方法的其余部分需要上下文时,您能否提供示例?
      • @cosset:当然。由于这是一个 UI 应用程序,因此需要上下文的示例是更新 UI 控件。例如,如果async 方法的末尾更新了LabelProgressBar,那么它需要在UI 上下文中恢复。如果没有,它会得到一个跨线程异常。
      • @ThanasisIoannidis:我的意思是我的async intro 中定义的“上下文”:当前的SynchronizationContextTaskScheduler。这是一个提示,但它在任务尚未完成时生效。如果任务已经完成(例如,从缓存中读取结果),它会更改上下文。如果您有必须在线程池上运行的代码,Task.Run 是更合适的工具。
      【解决方案4】:

      在这种情况下,您的 Task.Run 版本将有更多开销,因为第一个 await 调用 (await client.GetAsync(address)) 仍将封送回调用上下文,Task.Run 调用的结果也是如此。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-22
        • 1970-01-01
        相关资源
        最近更新 更多