"await task.ConfigureAwait(false)" 对比 "await ContextSwitcher.SwitchToThreadPool()"

"await task.ConfigureAwait(false)" versus "await ContextSwitcher.SwitchToThreadPool()"

广泛推荐这样使用 ConfigureAwait(false)

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

IIRC,同时人们普遍不鼓励使用这样的东西 ContextSwitcher,这会将异步执行流上下文切换到池线程,从而可能有助于避免 ConfigureAwait我的方法:

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

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

为什么第一个选项被认为是一个好的做法而这个不是,特别是考虑到 await Do1Async().ConfigureAwait(false) 之后的代码将在完全相同的条件下继续作为 await ContextSwitcher.SwitchToThreadPool() ?

之后的代码

还有一个选择:

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

IIRC,这仍然比 ContextSwitcher 选项好,但为什么呢?

最后还有这个有趣的做法:An alternative to ConfigureAwait(false) everywhere.

这是 the author's repoSynchronizationContextRemover 的相关部分:

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

像那样删除同步上下文是否安全,AFAIU 会在 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

这个 SynchronizationContextRemoverContextSwitcher 有何优势,此外也许它少了一个到池线程的切换?

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

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 同步完成,则存在差异。

Why is the 1st option considered a good practice and this one isn't

explained by Stephen Toub 一样,"switcher" 方法允许这样的代码:

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

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

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

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

  • await x.ConfigureAwait(false) 说 "I don't care what thread I resume on. It can be the same thread or a thread pool thread, whatever." 注意: 而不是 说 "switch to a thread pool thread."
  • await ContextSwitcher() 说 "Switch to this context and continue executing"。
  • await Task.Run(...) 说 "Run this code on a thread pool thread and then resume me."

在所有这些中,我更喜欢第一个和第三个。我用ConfigureAwait(false)表示"this method doesn't care about the thread it resumes on",我用Task.Run表示"run this code on a background thread"。我不喜欢 "switcher" 方法,因为我发现它使代码更难维护。

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

是;棘手的部分是它需要影响 synchronous 范围。这就是为什么没有 Func<Task>Func<Task<T>> overloads for my SynchronizationContextSwitcher.NoContext method,它们的作用几乎相同。 NoContextSynchronizationContextRemover 之间的一个主要区别是我的强制作用域(一个 lambda),其中没有上下文,并且移除器采用 "switcher" 的形式。因此,我再次强制代码说 "run this code without a context",而切换器说 "at this point in my method, remove the context, and then continue executing"。在我看来,明确范围的代码更易于维护(同样,考虑 catch/finally 块),这就是我使用 API.

风格的原因

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

SynchronizationContextRemoverNoContext 都在同一个线程上;他们只是暂时删除该线程上的上下文。 ContextSwitcher 确实切换到线程池线程。