为什么我懒得使用Task.ConfigureAwait(continueOnCapturedContext:false);

Yaw*_*aza 59 c# synchronizationcontext async-await

请考虑以下Windows窗体代码:

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

这里异常是在第3行引发的,因为在等待代码在非UI线程上执行之后.ConfigureAwait(false)有用吗?

小智 85

Stephen Cleary在这里有一个非常好的系列,你可以在这里找到,我引用了你的问题特定的部分:

大多数情况下,您不需要同步回"主"上下文.大多数异步方法都将在设计时考虑到组合:它们等待其他操作,每个操作本身代表一个异步操作(可以由其他操作组成).在这种情况下,您希望通过调用ConfigureAwait并传递来告知awaiter 捕获当前上下文,例如: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!";
}
Run Code Online (Sandbox Code Playgroud)

这个例子需要注意的重要一点是,异步方法调用的每个"级别"都有自己的上下文.DownloadFileButton_Click在UI上下文中启动,并调用DownloadFileAsync.DownloadFileAsync也是在UI上下文中开始,但后来通过调用走出了它的上下文ConfigureAwait(false).其余的DownloadFileAsync在线程池上下文中运行.但是,DownloadFileAsync完成并DownloadFileButton_Click恢复后,它在UI上下文恢复.

一个好的经验法则是使用,ConfigureAwait(false)除非你知道你确实需要上下文.

  • 如果建议使用`ConfigureAwait(false)`,为什么它不是默认为false? (78认同)
  • @JohnC因为它可能会产生错误(在某些非常特殊的情况下) - 与ConfigureAwait(true)相反,它将一直有效.请随意查看可用的fody插件,它们会自动更改代码中的所有等待者(因此更改默认设置). (5认同)
  • @NStuke这是可取的,因为你可能无法保证第一个异步运行的线程.如果代码有快速路径或缓存,它可以同步执行,因此在当前上下文中,而不是线程池上下文.这意味着第二个异步也将执行当前上下文并可能导致死锁.[这个答案](/sf/answers/3226617061/)有一个很好的解释. (3认同)
  • 谢谢维克多学习。很明显,当我们知道await之后的代码可以在任何线程上执行时,我们就可以使用ConfigureAwait(continueOnCapturedContext: false); 需要像另一个 Web 服务/IO 操作之类的东西,它可以在 UI 以外的任何线程上完成。由于我们在另一个线程而不是 UI 中,要更新 UI 控件,我们应该使用 Control.BeginInvoke() / Invoke 方法吗? (2认同)
  • 如果 DownloadFileAsync 的其余部分在线程池上下文中运行,则 WriteToDiskAsync 上是否需要ConfigureAwait(false)? (2认同)

pix*_*xel 14

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

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

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

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

当你写的时候await task;,这相当于写 await task.ConfigureAwait(true);。所以 true 是默认值。