我为什么要麻烦使用Task.ConfigureAwait(continueOnCapturedContext:false);


72

考虑以下Windows窗体代码:

private async void UpdateUIControlClicked(object sender, EventArgs e)
{
        this.txtUIControl.Text = "I will be updated after await - i hope!";
        await Task.Delay(5000).ConfigureAwait(continueOnCapturedContext: false);
        this.txtUIControl.Text = "I am updated now.";
}

此处在第三行引发异常,因为在等待之后,代码将在非UI线程上执行。在哪里ConfigureAwait(false)有用?


1
好的-在非UI环境中,什么线程继续执行其余方法都是无关紧要的。使用false时会影响性能吗?如果被迫在原始线程上执行继续操作,则可能需要等待做更多的工作(SynchronisationContext)。
Yawar Murtaza 2015年

1
阅读这篇文章。细节非常详细
Yuval Itzchakov 2015年

Answers:


106

Stephen Cleary对此有一个非常好的系列,您可以在这里找到,我引用了您的问题的特定文章:

大多数时候,您无需同步回“主要”上下文。大多数异步方法在设计时都会考虑到组合:它们等待其他操作,每个方法代表一个异步操作本身(可由其他人组成)。在这种情况下,您想通过调用ConfigureAwait并传递来告诉等待者不要捕获当前上下文,例如:false

private async Task DownloadFileAsync(string fileName)
{
  // Use HttpClient or whatever to download the file contents.
  var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);

  // Note that because of the ConfigureAwait(false), we are not on the original context here.
  // Instead, we're running on the thread pool.

  // Write the file contents out to a disk file.
  await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);

  // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.
}

// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the UI thread is not blocked by the file download.
  await DownloadFileAsync(fileNameTextBox.Text);

  // Since we resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded!";
}

在此示例中要注意的重要一点是,异步方法调用的每个“级别”都有其自己的上下文。DownloadFileButton_Click在UI上下文中启动,并称为DownloadFileAsyncDownloadFileAsync也从UI上下文开始,但随后通过调用退出其上下文ConfigureAwait(false)。其余的DownloadFileAsync运行在线程池上下文中。但是,DownloadFileAsync完成并DownloadFileButton_Click恢复时,它确实会在UI上下文恢复。

ConfigureAwait(false)除非您知道确实需要上下文,否则最好使用经验法则。


1
谢谢Victor Learned。显然,当我们知道可以在任何线程上执行await之后的代码时,可以使用ConfigureAwait(continueOnCapturedContext:false);。需要类似其他Web服务/ IO操作的操作,该操作可以在UI以外的任何线程上完成。由于我们位于UI之外的另一个线程中,因此要更新UI控件,是否应该使用Control.BeginInvoke()/ Invoke方法?
Yawar Murtaza 2015年

96
如果建议使用ConfigureAwait(false),那么为什么不默认将其设置为false?
约翰C,

5
@JohnC,因为它可能会产生错误(在某些非常特殊的情况下),这与可一直工作的ConfigureAwait(true)相反。随时检查可用的fody插件,它们会自动更改代码中的所有等待者(因此更改默认值)。
士力架(Snicker)

如果其余DownloadFileAsync在线程池上下文中运行,则WriteToDiskAsync上是否需要ConfigureAwait(false)?
NStuke

4
@NStuke这是可取的,因为您可能无法保证第一个异步运行在哪个线程中。如果代码具有快速路径或被缓存,则它可以同步执行,因此可以在当前上下文中而不是线程池上下文中执行。这意味着第二个异步也将执行当前上下文,并可能导致死锁。这个答案有一个很好的解释。
Ber'Zophus

8

您应该在服务中始终使用它,因为服务应该与UI无关。

但是,如果出现以下情况,请勿在服务之外使用它

  • 需要操纵UI或使用UI特定组件,例如Dispatcher或CoreDispatcher
  • 需要在ASP.net中使用HttpContext.Current

在这些情况下,请勿使用,ConfigureAwait(false)因为捕获当前上下文非常重要,否则应用程序会因尝试从非UI线程访问UI视图而崩溃

当您编写时await task;,这相当于编写等待task.ConfigureAwait(true);。因此默认值为true。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.