DEV Community

loading...

Why I no longer use ConfigureAwait(false)

noseratio profile image Andrew Nosenko Updated on ・3 min read

This might be an unpopular opinion, but I'd like to share why I no longer use ConfigureAwait(false) in pretty much any C# code I write, including libraries, unless required by team coding standards.

The two major benefits of using ConfigureAwait(false) and awaiting the returned ConfiguredTaskAwaitable vs the original Task are:

  • Potential performance improvements
  • Potential remedy for deadlocks

Let's get a closer look at each one.

Potential performance improvements

These may be a result of not posting the await continuations to the original synchronization context or task scheduler, but rather continuing in-place, on the same thread where the actual asynchronous operation has completed.

In my opinion, this is much less relevant with the modern ASP.NET Core/ASP.NET 5 back-end, which simply doesn't have a synchronization context by default anymore.

That may be different for the front-end code, which does have a concept of the main thread with certain UI framework-specific synchronization context on it. In this case, I prefer to explicitly control the execution context of the chain of asynchronous APIs I call, especially if it's a part of performance-sensitive code.

I do that by either using Task.Run(Func<Task> asyncFunc):

await Task.Run(async () => 
{
  await RunOneWorkflowAsync();
  await RunAnotherWorkflowAsync();
});
Enter fullscreen mode Exit fullscreen mode

Or, lately, with a custom implementation of TaskScheduler.SwitchTo extension, inspired by this GitHib issue:

await TaskScheduler.Default.SwitchTo();
await RunOneWorkflowAsync();
await RunAnotherWorkflowAsync();
Enter fullscreen mode Exit fullscreen mode

Instead of:

await RunOneWorkflowAsync().ConfigureAwait(false);
await RunAnotherWorkflowAsync();
Enter fullscreen mode Exit fullscreen mode

The former two might be more verbose and could incur an extra thread switch, but they clearly indicate the intent. Moreover, I don't have to worry about any side effects the current synchronization context/task scheduler may have on RunOneWorkflowAsync, and whether or not the author of RunOneWorkflowAsync used ConfigureAwait(false) internally in their implementation.

With the second option, TaskScheduler.Default.SwitchTo is optimized to check if the current thread is already a ThreadPool thread with the default task scheduler, and complete synchronously, if so.

Besides, there are rare corner cases when using ConfigureAwait(false) may actually introduce redundant thread/context switches. Here is some code to illustrate that and my old related question on SO.

Potential remedy for deadlocks

In my option, using ConfigureAwait(false) as a defensive measure against deadlock may actually hide obscure bugs. I prefer detecting deadlocks early. As a last resort, I'd still use Task.Run as a wrapper for deadlock-prone code. Here is a real-life example of where I needed that.

Moreover, from debugging and unit-testing prospective, we always have an option to install a custom SynchronizationContext implementation for debugging asynchronous code, and that would also require to give up ConfigureAwait(false).

Conclusion

Microsoft's Stephen Toub in his excellent "ConfigureAwait FAQ" still recommends using ConfigureAwait(false) for general-purpose, context-agnostic libraries, even if they only target .NET Core or later. Later in the comments to that blog post, David Fowler mentions that "most of ASP.NET Core doesn't use ConfigureAwait(false) and that was an explicit decision because it was deemed unnecessary."

Use your own best judgment on whether you need it or not for a specific project. I've personally chosen to avoid ConfigureAwait(false) where possible, and control the execution context explicitly where it makes sense.

I believe the library code should behave well in any context, and I don't like the idea of using ConfigureAwait(false) as a defensive remedy for undetected deadlocks. Performance-wise, where it is critical (like with ASP.NET Core), there is already no synchronisation context in modern .NET.

As an added bonus, my C# code looks more clean without ConfigureAwait(false) all over the place :-)

Discussion (4)

Collapse
erdemyavuzyildiz profile image
ERDEM YAVUZ YILDIZ

Correct, It's not library's responsibility to take defense against certain situations while also limiting it's own usability.
Developers should be aware of any synchronization context in use and code accordingly.
Library developer's decisions can't remedy for that. Trying to solve developer's problem with a library is bad practice alright.

Developer's should be also free to develop their own synchronization context and call library functions within that context.

Collapse
noseratio profile image
Collapse
paulomorgado profile image
Paulo Morgado

ConfigureAwait(false) is about not returning back to the synchronization context when an awaiter completes.

It's about performance, not deadlocks.

Collapse
noseratio profile image
Andrew Nosenko Author

I believe I've covered that in the article in this section:

• Potential performance improvements

Forem Open with the Forem app