While all await expressions capture the execution context, the decision of whether to flow synchronization context as well is controlled by the type being awaited. If you await for a Task, the synchronization context will also be captured by default. Tasks are not the only thing you can await, and I’ll describe how types can support await in the section “The await Pattern”.
Sometimes, you might want to avoid getting the synchronization context (internal another tread switch) involved. If you want to perform asynchronous work starting from a UI thread, but you have no particular need to remain on that thread, scheduling every continuation through the synchronization context is unnecessary overhead.
If the asynchronous operation is a Task or Task<T> (or the equivalent value types, ValueTask or ValueTask<T>), you can declare that you don’t want this by calling the ConfigureAwait method passing false. This returns a different representation of the asynchronous operation, and if you await that instead of the original task, it will ignore the current SynchronizationContext if there is one.
ConfigureAwait
This code is a click handler for a button, so it initially runs on a UI thread. It retrieves the Text property from a couple of text boxes. Then it kicks off some asynchronous work—fetching the content for a URL and copying the data into a file.
Do not use ConfigureAwait(false) when the code immediately after the await updates the UI
Various asynchronous APIs introduced in Windows as part of the UWP API return an IAsyncOperation<T> instead of Task<T>. This is because UWP is not .NET-specific, and it has its own runtime-independent representation for asynchronous operations that can also be used from C++ and JavaScript. This interface is conceptually similar to TPL tasks, and it supports the await pattern, meaning you can use await with these APIs. However, it does not provide ConfigureAwait. If you want to do something similar to with one of these APIs, you can use the AsTask extension method that wraps an IAsyncOperation<T> as a Task<T>, and you can call ConfigureAwait on that task instead.
If you are writing libraries, then in most cases you should call ConfigureAwait(false) anywhere you use await. This is because continuing via the synchronization context can be expensive, and in some cases it can introduce the possibility of deadlock occurring. The only exceptions are when you are doing something that positively requires the synchronization context to be preserved, or you know for certain that your library will only ever be used in application frameworks that do not set up a synchronization context. (E.g., ASP.NET Core applications do not use synchronization contexts, so it generally doesn’t matter whether or not you call ConfigureAwait(false) in those.)
ref: Programming C# 8.0