为什么总是推荐使用 await 在每一行上写 ConfigureAwait(false) ,我真的需要它吗?

Vik*_*nov 4 c# async-await .net-core

问题不在于 ConfigureAwait 做什么。但更确切地说,为什么我到处都能看到类似的东西

一般来说,是的。除非该方法需要其上下文,否则应为每个等待使用 ConfigureAwait(false)。

即他们建议我应该写

await Method1().ConfigureAwait(false);
await Method2().ConfigureAwait(false);
// Do something else
// ...
await Method3().ConfigureAwait(false);
await Method4().ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

但在这种情况下,只在一开始就重置上下文不会更清楚,就像

await Task.Yield().ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

它保证下面的代码将在没有同步上下文的情况下执行,不是吗?

即我读到如果该方法立即返回,则写一次 ConfigureAwait 可能不起作用。对我来说,显而易见的解决方案看起来像调用 ConfigureAwait(false) 肯定不会立即返回的东西,Task.Yield 是什么,对吧?

另外我知道 Task.Yield 不再包含 ConfigureAwait(不知道为什么,因为我知道它以前曾经有它),但是查看 Task.Yield 代码很容易编写自己的方法除了用空的同步上下文调用延续之外,什么都不做。

对我来说,阅读似乎要容易得多,尤其是当你写一次的时候写

await TaskUtility.ResetSyncContext();
Run Code Online (Sandbox Code Playgroud)

而不是在每一行上写 ConfigureAwait。

这会起作用(Task.Yield().ConfigureAwait(false) 或类似的自定义方法)还是我错过了什么?

Gab*_*uci 7

一般来说,是的。除非该方法需要其上下文,否则应为每个等待使用 ConfigureAwait(false)。

我经常在 Stack Overflow 上看到这样的建议,甚至 Stephen Cleary(微软 MVP)在他的Async 和 Await文章中也说过:

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

斯蒂芬肯定知道他的东西,我同意这个建议在技术上是准确的,但我一直认为这是一个糟糕的建议,原因有两个:

  1. 初学者,和
  2. 维修风险

首先,这对初学者来说是个坏建议,因为同步上下文是一个复杂的主题。如果您开始学习async/await被告知“除非方法需要其上下文,否则ConfigureAwait(false)应该用于每个await”,但您甚至不知道“上下文”是什么以及“需要它”意味着什么,那么您就不会知道什么时候不应该使用它,所以你最终总是使用它。这意味着你可能会遇到很难弄清楚的错误,除非你碰巧知道,是的,你确实需要那个“上下文”的东西,而这个神奇的“ConfigureAwait”东西让你失去了它。您可能会花费数小时试图弄清楚这一点。

对于任何类型的应用程序,我认为建议确实应该相反:根本不要使用ConfigureAwait,除非您知道它的作用并且您已经确定在该行之后绝对不需要上下文。

但是,确定您不需要上下文可以是简单的,也可以是非常复杂的,具体取决于之后调用的方法。但即便如此-这是第二个原因我那个建议不同意-只是因为你并不需要这一行后的背景下,现在,这并不意味着一些代码不会在以后添加,将使用的上下文。您必须希望做出这种改变的人知道做什么ConfigureAwait(false),看到它并删除它。ConfigureAwait(false)随处使用会产生维护风险。

这是另一位 Stephen Toub(Microsoft 员工)在ConfigureAwait 常见问题解答中的“何时应该使用 ConfigureAwait(false)?”副标题下的建议:

在编写应用程序时,您通常需要默认行为(这就是为什么它是默认行为)。...这导致了以下一般指导:如果您正在编写应用程序级代码,请不要使用ConfigureAwait(false)

在我自己的应用程序代码中,我不会费心去弄清楚哪些地方可以使用,哪些地方不能使用。我只是忽略它的ConfigureAwait存在。当然,通过尽可能使用它可以提高性能,但我真的怀疑它对任何人来说都会有明显的差异,即使它可以通过计时器进行测量。我不相信投资回报是积极的。

唯一的例外是在编写库时,正如 Stephen Toub 在他的文章中指出的那样:

如果您正在编写通用库代码,请使用 ConfigureAwait(false)

这有两个原因:

  1. 库不知道它正在使用的应用程序的上下文,因此无论如何它都不能使用上下文,并且
  2. 如果使用库的人决定同步等待您的异步库代码,则可能导致他们无法更改的死锁,因为他们无法更改您的代码。(理想情况下,他们不应该这样做,但它可能会发生)

要解决您问题中的另一点:ConfigureAwait(false)在第一个await而不是其余部分上使用并不总是足够的。使用它的每一个 await库中的代码。Stephen Toub 的文章标题为“是否可以仅在我的方法中的第一个 await 上使用 ConfigureAwait(false) 而不是在其余部分上使用?” 部分说:

如果await task.ConfigureAwait(false)涉及到在等待时已经完成的任务(这实际上非常常见),那么ConfigureAwait(false)将毫无意义,因为线程在此之后继续执行方法中的代码,并且仍然处于与之前相同的上下文中。

  • 我相信我会更新那篇“异步”博客文章。在过去的几年里,我还建议仅对库代码使用“ConfigureAwait(false)”。 (14认同)
  • @HardyHobeck我同意代码应该清晰且不言自明,但“ConfigureAwait(false)”两者都不是,因此这个问题以及许多很多类似的问题。但正如我在回答中所说,它需要仔细考虑才能使用,但没有明显的好处。这就是为什么我建议永远不要使用它(除了在图书馆中)。我只是不认为付出这种努力有什么好处。当涉及到死锁时,如果 `ConfigureAwait(false)` 为您“解决”了死锁,那么您就做错了其他事情。 (3认同)
  • 同意。我想补充一点,在接受并执行调用者提供的 lambda 的库代码中,脱离上下文调用 lambda 对于调用者来说可能是意外的(因为例如他们可能访问 lambda 内 UI 元素的属性)。因此,即使在库代码中,无条件的“ConfigureAwait(false)”也可能会出现问题。 (2认同)
  • @JeremyLakeman **ASP**.NET Core 不会,但 .NET Core 一般*可以*。[ConfigureAwait 常见问题解答](https://devblogs.microsoft.com/dotnet/configureawait-faq/) 文章中也介绍了这一点。 (2认同)