1

It's widely recommended to use ConfigureAwait(false) like this:

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

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();
// ...

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() ?

Also, there is another alternative:

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

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

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

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);
    }
}

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

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

TylerH
  • 20,799
  • 66
  • 75
  • 101
avo
  • 10,101
  • 13
  • 53
  • 81
  • 4
    "Widely encouraged" is probably too strong of a statement - https://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code... It was never exactly encouraged except in library code where author knows that they don't need context... And even less useful in Core... – Alexei Levenkov Nov 28 '19 at 23:15
  • 2
    These concerns are no longer important for ASP.NET Core (no synchronization context there), but perhaps are still valid for UI apps. I've personally [stopped using `ConfigureAwait(false)` even in my libraries](https://stackoverflow.com/a/59002568/1768303) and use the `await Task.Run(async () => { ... })` option in my front end code where I might benefit from reducing the amount of context switching. – noseratio Nov 29 '19 at 00:36
  • @noseratio, could you explain what's wrong with `ContextSwitcher.SwitchToThreadPool` nowadays? Lots of things have changed since Async CTP. – avo Nov 29 '19 at 03:15
  • 1
    Honestly I don't think there's anything wrong with `SwitchToThreadPool` or the likes, @avo. There's an [open issue to add `ThreadPool.Yield` awaitable](https://github.com/dotnet/corefx/issues/15490) to .NET, by David Fowler himself, with [Stephen Toub's comment](https://github.com/dotnet/corefx/issues/15490#issuecomment-275270616). Also check [this issue](https://github.com/dotnet/corefx/issues/23898). I asked a [similar question](https://stackoverflow.com/q/18779393) a while ago, too. – noseratio Nov 29 '19 at 05:54
  • 1
    @noseratio thank you, lots of good information in that thread and it looks like .NET team will be adding `ThreadPool.SwitchTo()` awaitable to .NET 5.0! Maybe you should put that as an answer? – avo Nov 29 '19 at 08:36
  • 1
    FYI, Stephen Toub has just published **[ConfigureAwait FAQ](https://devblogs.microsoft.com/dotnet/configureawait-faq/)** and it's a must-read. – noseratio Dec 12 '19 at 21:29

1 Answers1

4

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.

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() ?

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

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

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
}

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, 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) 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."

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.

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

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.

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

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen for your exemplary answer! It appears .NET team [is bringing back `ThreadPool.SwitchTo`](https://github.com/dotnet/corefx/issues/15490) as `await` inside `try/catch` is no longer a problem in modern C#. Could you please explain why you consider this option less maintainable? – avo Nov 30 '19 at 11:15
  • BTW, that issue thread also has a comment by Stephen Toub where he now says: *"We removed these because you couldn't use an await inside of a catch/finally [...] However, C# has since gained the ability to have awaits in catch/finally blocks, making that largely a moot argument."* – avo Nov 30 '19 at 11:19
  • Maybe I didn't understand the point about an unknown thread inside `catch`, especially because `ConfigureAwait` is no different if completed asynchronously: `try { await Do1Async().ConfigureAwait(false); } catch (Exception) { /* Unknown thread* /}`. But i guess I see the point now: we aren't normally supposed to catch exceptions inside a library code, and libraries is where `ConfigureAwait` is normally used... am I right? – avo Nov 30 '19 at 11:29
  • 1
    @avo: My point about maintainability is from many years of multithreading experience: long ago, I adopted the position that each method (or more precisely, each scoped code block) should have a single context. This rule limits the number of things you need to keep in your head when reading the code. Having a method switch contexts in the middle of executing adds conceptual overhead. Also, `SwitchTo` methods for other contexts encourage less testable code (not a problem with `ThreadPool.SwitchTo`). – Stephen Cleary Dec 02 '19 at 13:17