为什么总是推荐使用 await 在每一行上写 ConfigureAwait(false) ,我真的需要它吗? [英] Why is writing ConfigureAwait(false) on every line with await always recommended and do I really need it?

查看:28
本文介绍了为什么总是推荐使用 await 在每一行上写 ConfigureAwait(false) ,我真的需要它吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The question is not about what ConfigureAwait does. But rather why literally everywhere I see something like

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

As a general rule, yes. ConfigureAwait(false) should be used for every await unless the method needs its context.

即他们建议我应该写

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

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

But in such case wouldn't be clearer just resetting context in the very beginning just once like

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

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

It guarantees that the code below will be executed with no sync context, doesn't it?

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

I.e. I read that writing ConfigureAwait once might not work if the method returns immediately. And for me the obvious solution looks like call ConfigureAwait(false) on something that for sure doesn't return immediately, which Task.Yield is, right?

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

Also as I know the Task.Yield doesn't contain ConfigureAwait anymore(don't know why, as I know it used to have it before), but looking at the Task.Yield code it is pretty easy to write your own method which would do nothing more but calling the continuation with an empty sync context.

对我来说,读起来要容易得多,尤其是写一次时写起来

And for me it seems much much easier to read and especially to write when you write one time

await TaskUtility.ResetSyncContext();

而不是在每一行上写 ConfigureAwait.

than writing ConfigureAwait on every line.

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

Will that work(Task.Yield().ConfigureAwait(false) or similar custom method) or I miss something?

推荐答案

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

As a general rule, yes. ConfigureAwait(false) should be used for every await unless the method needs its context.

我经常在 Stack Overflow 上看到这样的建议,甚至 Stephen Cleary(微软 MVP)在他的 异步和等待 文章:

I've seen that advice often here on Stack Overflow, and it's even what Stephen Cleary (a Microsoft MVP) says in his Async and Await article:

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

A good rule of thumb is to use ConfigureAwait(false) unless you know you do need the context.

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

Stephen definitely knows his stuff, and I agree that the advice is technically accurate, but I've always thought that this is bad advice for two reasons:

  1. 初学者和
  2. 维护风险

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

First, it's bad advice for beginners because synchronization context is a complex subject. If you start learning async/await by being told that "ConfigureAwait(false) should be used for every await unless the method needs its context", but you don't even know what "context" is and what it means to "need it", then you don't know when you shouldn't use it, so you end up always using it. That means you can run into bugs that will be very difficult to figure out unless you happen to learn that, yes, you did actually need that "context" thing and this magical "ConfigureAwait" thing made you lose it. You can lose hours trying to figure that out.

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

For applications of any kind, I believe the advice really should be the opposite: Don't use ConfigureAwait at all, unless you know what it does and you have determined that you absolutely don't need the context after that line.

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

However, determining you don't need the context can be either simple, or quite complex depending on what methods are called after. But even then - and this is the second reason I disagree with that advice - just because you don't need the context after that line right now, doesn't mean some code won't be added later that will use the context. You'll have to hope that whoever makes that change knows what ConfigureAwait(false) does, sees it, and removes it. Using ConfigureAwait(false) everywhere creates a maintenance risk.

这是另一位 Stephen Toub(Microsoft 员工)在 ConfigureAwait 常见问题解答中推荐的内容 在副标题我什么时候应该使用 ConfigureAwait(false)?"下:

This is what another Stephen, Stephen Toub (a Microsoft employee), recommends in the ConfigureAwait FAQ under the subheading "When should I use ConfigureAwait(false)?":

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

When writing applications, you generally want the default behavior (which is why it is the default behavior). ... This leads to the general guidance of: if you’re writing app-level code, do not use ConfigureAwait(false)

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

In my own application code, I don't bother trying to figure out where I can and can't use it. I just ignore that ConfigureAwait exists. Sure, there can be a performance improvement by using it where you can, but I really doubt that it will be a noticeable difference to any human, even if it is measurable by a timer. I don't believe the return on investment is positive.

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

The only exception to this is when you're writing libraries, as Stephen Toub points out in his article:

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

有两个原因:

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

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

To address another point in your question: it's not always enough to use ConfigureAwait(false) on the first await and not the rest. Use it on every await in your library code. Stephen Toub's article under the heading "Is it ok to use ConfigureAwait(false) only on the first await in my method and not on the rest?" says, in part:

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

If the await task.ConfigureAwait(false) involves a task that’s already completed by the time it’s awaited (which is actually incredibly common), then the ConfigureAwait(false) will be meaningless, as the thread continues to execute code in the method after this and still in the same context that was there previously.

这篇关于为什么总是推荐使用 await 在每一行上写 ConfigureAwait(false) ,我真的需要它吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆