19

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

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

I.e. they propose that I should write

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?

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?

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

than writing ConfigureAwait on every line.

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

Viktor Arsanov
  • 1,285
  • 1
  • 9
  • 17
  • Related: [Why would I bother to use Task.ConfigureAwait(continueOnCapturedContext: false);](https://stackoverflow.com/questions/27851073/why-would-i-bother-to-use-task-configureawaitcontinueoncapturedcontext-false) – Theodor Zoulias Jun 21 '20 at 15:15
  • 1
    It used to be the norm in the .NET world. Notice that, unless you're targeting WPF/WinForms with .NET Core 3.1, there's no SynchronizationContext in a normal .NET Core or ASP.NET Core application. If you're writing ASP.NET Core code, you can safely ignore that – Camilo Terevinto Jun 21 '20 at 15:19
  • 2
    @CamiloTerevinto, there's, usually, no synchronization context in ASP.NET Core. WPF and Windows Forms run now on .NET Core and have synchronization contexts. And nothing prevents you from adding a synchronization context to ASP.NET Core. – Paulo Morgado Jun 21 '20 at 17:20
  • [ConfigureAwait FAQ | .NET Blog](https://devblogs.microsoft.com/dotnet/configureawait-faq/) – Paulo Morgado Jun 21 '20 at 17:21

1 Answers1

40

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

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:

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. Beginners, and
  2. Maintenance risk

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.

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.

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.

This is what another Stephen, Stephen Toub (a Microsoft employee), recommends in the ConfigureAwait FAQ under the subheading "When should I use 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)

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.

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

if you’re writing general-purpose library code, use ConfigureAwait(false)

That's for two reasons:

  1. A library has no idea about the context of the application it's being used in, so it can't use the context anyway, and
  2. If the person using the library decides to wait synchronously on your asynchronous library code, it could cause a deadlock that they cannot change because they can't change your code. (ideally, they shouldn't do that, but it can happen)

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:

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.

Edit: I finally got around to making this into an article on my site: Don’t use ConfigureAwait(false)

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • 1
    Agreed. I would add that in library code that accepts and executes lambdas supplied by the caller, invoking the lambda out of context could be unexpected for the caller (because for example they may access properties of UI elements inside the lambda). So having an unconditional `ConfigureAwait(false)` could be problematic even in library code. – Theodor Zoulias Jun 22 '20 at 00:05
  • Interestingly the Polly library [solves this problem](https://github.com/App-vNext/Polly/wiki/Asynchronous-action-execution#synchronizationcontext) by providing APIs with a `continueOnCapturedContext` argument, with the default being `false` instead of `true`. So by default a [`Retry`](https://github.com/App-vNext/Polly/wiki/Retry) policy will invoke the lambda with sync context the first time, and without sync context the retries. – Theodor Zoulias Jun 22 '20 at 00:06
  • Also note the .net-core tag, which doesn't have a sync context... – Jeremy Lakeman Jun 22 '20 at 02:21
  • 1
    @JeremyLakeman **ASP**.NET Core doesn't, but .NET Core in general *can*. That's something covered in that [ConfigureAwait FAQ](https://devblogs.microsoft.com/dotnet/configureawait-faq/) article too. – Gabriel Luci Jun 22 '20 at 04:07
  • Thanks you. And regarding "it's not always enough to use ConfigureAwait(false) on the first await and not the rest." But it is enough to use ConfigureAwait(false) on the first await of Task.Yield(), isn't it? – Viktor Arsanov Jun 22 '20 at 12:41
  • "If the await task.ConfigureAwait(false) involves a task that’s already completed by" so this should work if task.ConfigureAwait involves a task that can never be completed by the time it is awaited, which is the case for Task.Yield, or I understand Task.Yield wrong? And let's assume Task.Yield() has ConfigureAwait method. – Viktor Arsanov Jun 22 '20 at 12:42
  • @ViktorArsanov You are correct: `Task.Yield()` is guaranteed to be an incomplete `Task`. – Gabriel Luci Jun 22 '20 at 15:30
  • 9
    I believe I will update that `async` blog post. For the last several years, I have also recommended only using `ConfigureAwait(false)` for library code. – Stephen Cleary Jun 23 '20 at 01:32
  • @GabrielLuci great, because writing ConfigureAwait on every line always looked for me as a workaround, caused by the fact that a task might be in complete state, and whenever I propose Task.Yield().ConfigureAwait(false) I’m told that on SO they don’t say anything about it. – Viktor Arsanov Jun 23 '20 at 14:36
  • Code should be clear and the intention needs to be expressed so everyone is knowing what the code does. If you don't do this then the code is not accurate and wrong in my opinion. 1. Beginnners: Not using a feature or using a feature with a different intention because some beginner could use it somewhere else in a wrong way is not a good advice. 2. Maintenance Risk: Normally the code needs to be self-explained without any separation of concern violations. Anyway, instead of explaining a fictive scenario in future you should explain problems which might happening in present (deadlocks). – Hardy Hobeck Jul 05 '23 at 11:38
  • 1
    @HardyHobeck I agree that code should be clear and self-explained, but `ConfigureAwait(false)` is neither, hence this question and many, many more like it. But as I said in my answer, it requires careful thought to use, but with no discernible benefit. This is why I suggest to never use it (except in libraries). I just don't see the up side to putting in that effort. When it comes to deadlocks, if `ConfigureAwait(false)` "solves" a deadlock for you, then you've done something else wrong. – Gabriel Luci Jul 05 '23 at 12:30
  • 1
    @GabrielLuci Thanks for your answer. Just for clarification: It's not about fixing deadlocks. It's about writing defensive code which prevent deadlocks (second line of defense). Only one line of code needs to be not asynchronous to get a deadlock. Think about it if you are maintaining 100 of thousand lines of code of an application with half a dozen third party libraries. In my opinion Microsoft should consider this and give the developer the possibility (on every DLL) to specify the behavior of await if no ConfigureAwait is present. But again the intention is important. – Hardy Hobeck Jul 05 '23 at 12:47