“ await task.ConfigureAwait(false)”。对比“ await ContextSwitcher.SwitchToThreadPool()” [英] "await task.ConfigureAwait(false)" versus "await ContextSwitcher.SwitchToThreadPool()"

查看:86
本文介绍了“ await task.ConfigureAwait(false)”。对比“ await ContextSwitcher.SwitchToThreadPool()”的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

广泛建议使用 ConfigureAwait(false),例如:

await Do1Async().ConfigureAwait(false);
// ...
await Do2Async().ConfigureAwait(false);
// ...
await Do3Async().ConfigureAwait(false);
// ...

IIRC,同时强烈建议不要使用像这样 ContextSwitcher ,它将异步执行流上下文切换到池线程,从而可能有助于避免在我的方法中出现 ConfigureAwait 感染:

IIRC, at the same time it's widely discouraged to use something like this ContextSwitcher, which would switch the async execution flow context to a pool thread and thus might help avoiding that ConfigureAwait infestation across my method:

await ContextSwitcher.SwitchToThreadPool(); // this was even removed from async CTP

await Do1Async();
// ...
await Do2Async();
// ...
await Do3Async();
// ...

为什么第一种选择被认为是一种好习惯而这不是,特别是考虑到 await Do1Async()。ConfigureAwait(false)之后的代码将在与代码完全相同的条件下继续的事实在等待ContextSwitcher.SwitchToThreadPool()之后?

Why is the 1st option considered a good practice and this one isn't, especially given the fact the code after await Do1Async().ConfigureAwait(false) will continue on exactly the same conditions as the code after await ContextSwitcher.SwitchToThreadPool() ?

此外,还有另一种选择

await Task.Run(async () => {
   await Do1Async();
   // ...
   await Do2Async();
   // ...
   await Do3Async();
   // ...
});

IIRC,它仍然比 ContextSwitcher 选项,但是为什么呢?

IIRC, this is still better than the ContextSwitcher option, but why?

最后,仍然有一种有趣的方法:到处都是ConfigureAwait(false)的替代方法

Finally, there is still this interesting approach: An alternative to ConfigureAwait(false) everywhere.

这是SynchronizationContextRemover 的相关部分master / ConfigureAwaitBehavior / ExtremeConfigAwaitLibrary / SynchronizationContextRemover.cs rel = nofollow noreferrer>作者的回购:

Here is the relevant part of SynchronizationContextRemover from the author's repo:

public void OnCompleted(Action continuation)
{
    var prevContext = SynchronizationContext.Current;
    try
    {
        SynchronizationContext.SetSynchronizationContext(null);
        continuation();
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(prevContext);
    }
}

像这样删除同步上下文是否安全? ,在等待新的SynchronizationContextRemover()吗?

Is it safe to just remove the synchronization context like that, which AFAIU would affect the whole synchronous scope after await new SynchronizationContextRemover() ?

await new SynchronizationContextRemover();
// we are still on the same thread 
// but the synchronization context has been removed, 
// be careful...
// ...
await Do1Async();
// ...until now

SynchronizationContextRemover ContextSwitcher 好,除了也许它对池线程的切换少了一点?

How is this SynchronizationContextRemover better than ContextSwitcher, besides perhaps it has one less switch to a pool thread?

推荐答案

正如其他人所指出的,对于现代代码, ConfigureAwait(false)不太必要(特别是, (因为ASP.NET Core已成为主流)。此时是否在您的库中使用它是一个判断调用;就个人而言,我仍然会使用它,但是我的主要异步库是非常底层的。

As others have noted, ConfigureAwait(false) is less necessary with modern code (in particular, since ASP.NET Core has gone mainstream). Whether to use it in your library at this point is a judgement call; personally, I still do use it, but my main async library is very low-level.


尤其是考虑到<$之后的代码c $ c> await Do1Async()。ConfigureAwait(false)将在与 await ContextSwitcher.SwitchToThreadPool()之后的代码完全相同的条件下继续运行?

especially given the fact the code after await Do1Async().ConfigureAwait(false) will continue on exactly the same conditions as the code after await ContextSwitcher.SwitchToThreadPool() ?

条件并不完全相同-如果 Do1Async 同步完成。

The conditions aren't exactly the same - there's a difference if Do1Async completes synchronously.


为什么第一个选项被认为是一种好习惯,而这个不是? t

中, switcher方法确实允许代码像这样:

As explained by Stephen Toub, the "switcher" approach does allow code like this:

try
{
  await Do1Async(); // UI thread
  await ContextSwitcher.SwitchToThreadPool();
  await Do2Async(); // Thread pool thread
}
catch (Exception)
{
  ... // Unknown thread
}

具体来说, catch finally 个块可以在未知线程上下文中运行,具体取决于运行时行为。可以在多个线程上运行的代码很难维护。这是它被从Async CTP中删除的主要原因(还请记住,当时使用该语言,您不能在 $ > catch 或 finally ,因此您无法切换到所需的上下文)。

Specifically, catch and finally blocks can run in an unknown thread context, depending on runtime behavior. Code that can run on multiple threads is harder to maintain. This is the main reason it was cut from the Async CTP (also bearing in mind that with the language at that time, you couldn't await in a catch or finally, so you couldn't switch to the desired context).

IIRC,它仍然比 ContextSwitcher 选项要好,但是为什么呢?

IIRC, this is still better than the ContextSwitcher option, but why?

我认为重要的是要注意这些都是不同的语义-特别是它们都说了完全不同的事情:

I think it's important to note that these are all different semantics - in particular, they are all saying quite different things:


  • await x.ConfigureAwait(false)说:我不在乎我继续执行哪个线程。它可以相同线程或线程池线程,等等。 注意:它不会说切换到线程池线程。

  • await ContextSwitcher() 说切换到此上下文并继续执行。

  • 等待Task.Run(...)说在线程池线程上运行此代码,然后恢复我。

  • await x.ConfigureAwait(false) says "I don't care what thread I resume on. It can be the same thread or a thread pool thread, whatever." Note: it does not say "switch to a thread pool thread."
  • await ContextSwitcher() says "Switch to this context and continue executing".
  • await Task.Run(...) says "Run this code on a thread pool thread and then resume me."

在所有这些中,我更喜欢第一个第三。我使用 ConfigureAwait(false)表示此方法不关心其继续执行的线程,而我使用的是 Task.Run 说在后台线程上运行此代码。我不喜欢切换器方法,因为我发现它使代码的可维护性降低。

Out of all of those, I prefer the first and third. I use ConfigureAwait(false) to say "this method doesn't care about the thread it resumes on", and I use Task.Run to say "run this code on a background thread". I don't like the "switcher" approach because I find it makes the code less maintainable.


删除同步是否安全?这样的上下文,在等待新的SynchronizationContextRemover()吗?


$ b之后,哪个AFAIU将影响整个同步范围$ b

是;棘手的部分是它需要影响 synchronous 范围。这就是为什么没有 Func< Task> Func< Task< T>> 我的 SynchronizationContextSwitcher的重载。 NoContext 方法,其作用几乎相同。 NoContext SynchronizationContextRemover 之间的主要区别是我的强制作用域(lambda)中没有上下文去除剂为切换器形式。因此,我再次强迫代码说在没有上下文的情况下运行此代码,而切换器说此时,在我的方法中,删除上下文,然后继续执行。在我看来,显式作用域的代码更具可维护性(再次考虑 catch / finally 块),这是为什么我使用这种风格的API。

Yes; the tricky part is that it needs to affect the synchronous scope. This is why there is no Func<Task> or Func<Task<T>> overloads for my SynchronizationContextSwitcher.NoContext method, which does pretty much the same thing. The one major difference between NoContext and SynchronizationContextRemover is that mine forces a scope (a lambda) in which there is no context and the remover is in the form of a "switcher". So again, mine forces the code to say "run this code without a context", whereas the switcher says "at this point in my method, remove the context, and then continue executing". In my opinion, the explicitly-scoped code is more maintainable (again, considering catch/finally blocks), which is why I use that style of API.


SynchronizationContextRemover 怎么样比 ContextSwitcher 更好,也许它对池线程的切换要少一些?

How is this SynchronizationContextRemover better than ContextSwitcher, besides perhaps it has one less switch to a pool thread?

SynchronizationContextRemover NoContext 都位于同一线程上;他们只是暂时删除该线程上的上下文。 ContextSwitcher 实际上确实切换到线程池线程。

SynchronizationContextRemover and NoContext both stay on the same thread; they just temporarily remove the context on that thread. ContextSwitcher does actually switch to a thread pool thread.

这篇关于“ await task.ConfigureAwait(false)”。对比“ await ContextSwitcher.SwitchToThreadPool()”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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