为什么总是建议在每一行写ConfigureAwait(false)并使用await,我真的需要它吗



问题不在于ConfigureAwait做什么。但为什么我在任何地方都能看到类似的东西

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

即他们建议我写

await Method1().ConfigureAwait(false);
await Method2().ConfigureAwait(false);
// Do something else
// ...
await Method3().ConfigureAwait(false);
await Method4().ConfigureAwait(false);

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

await Task.Yield().ConfigureAwait(false);

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

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

正如我所知,Task.Ield不再包含ConfigureAwait(不知道为什么,因为我知道它以前也有),但看看Task.Ield代码,很容易编写自己的方法,它只会用空的同步上下文调用continuation。

对我来说,这似乎更容易阅读,尤其是当你写一次时

await TaskUtility.ResetSyncContext();

而不是在每一行上写入ConfigureAwait。

这行得通吗(Task.Ielder().ConfigureAwait(false)或类似的自定义方法),或者我错过了什么?

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

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

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

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

  1. 初学者,以及
  2. 维护风险

首先,这对初学者来说是个不好的建议,因为同步上下文是一个复杂的主题。如果你开始学习async/CCD_;CCD_ 3应当用于每个CCD_;,但你甚至不知道";上下文";是什么以及它对";需要它";,然后你不知道什么时候不应该使用它,所以你最终总是用它。这意味着你可能会遇到很难弄清楚的错误,除非你碰巧学会了,是的,你确实需要它;上下文";事情和这个神奇的";ConfigureAwait";事情让你失去了它。你可能会花上几个小时试图弄清楚。

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

然而,根据后面调用的方法,确定您不需要上下文可能很简单,也可能很复杂。但即便如此——这也是我不同意该建议的第二个原因——仅仅因为你现在不需要这一行之后的上下文,并不意味着以后不会添加一些使用上下文的代码。你必须希望,无论是谁做出了改变,都知道ConfigureAwait(false)做了什么,看到了它,并删除了它。在任何地方使用ConfigureAwait(false)都会带来维护风险。

这是另一位Stephen,Stephen Toub(微软员工)在ConfigureAwait常见问题解答副标题";我应该何时使用ConfigureAwait(false)":

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

在我自己的应用程序代码中,我不想弄清楚哪里可以使用它,哪里不能使用它。我只是忽略了ConfigureAwait的存在。当然,通过在可能的地方使用可以改善性能,但我真的怀疑它对任何人来说都会有明显的不同,即使它是可以通过计时器测量的。我认为投资回报率不是正的。

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

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

这有两个原因:

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

要解决问题中的另一点:在第一个await上使用ConfigureAwait(false)并不总是足够的,而不是在其余的上。在库代码中的每个await上使用它。Stephen Toub的文章标题为";在我的方法中,只在第一个等待时使用ConfigureAwait(false),而不在其余的等待时使用它可以吗"部分说:

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

编辑:我终于抽出时间在我的网站上写了一篇文章:不要使用ConfigureAwait(false)

最新更新