ConfigureAwait FAQ
贼好的一篇文章,收藏收藏。
ConfigureAwait FAQ
Avatar
Stephen
December 11th, 2019
.NET added async/await to the languages and libraries over seven years ago. In that time, it’s caught on like wildfire, not only across the .NET ecosystem, but also being replicated in a myriad of other languages and frameworks. It’s also seen a ton of improvements in .NET, in terms of additional language constructs that utilize asynchrony, APIs offering async support, and fundamental improvements in the infrastructure that makes async/await tick (in particular performance and diagnostic-enabling improvements in .NET Core).
However, one aspect of async/await that continues to draw questions is ConfigureAwait. In this post, I hope to answer many of them. I intend for this post to be both readable from start to finish as well as being a list of Frequently Asked Questions (FAQ) that can be used as future reference.
To really understand ConfigureAwait, we need to start a bit earlier…
What is a SynchronizationContext?
The System.Threading.SynchronizationContext docs state that it “Provides the basic functionality for propagating a synchronization context in various synchronization models.” Not an entirely obvious description.
For the 99.9% use case, SynchronizationContext is just a type that provides a virtual Post method, which takes a delegate to be executed asynchronously (there are a variety of other virtual members on SynchronizationContext, but they’re much less used and are irrelevant for this discussion). The base type’s Post literally just calls ThreadPool.QueueUserWorkItem to asynchronously invoke the supplied delegate. However, derived types override Post to enable that delegate to be executed in the most appropriate place and at the most appropriate time.
For example, Windows Forms has a SynchronizationContext-derived type that overrides Post to do the equivalent of Control.BeginInvoke; that means any calls to its Post method will cause the delegate to be invoked at some later point on the thread associated with that relevant Control, aka “the UI thread”. Windows Forms relies on Win32 message handling and has a “message loop” running on the UI thread, which simply sits waiting for new messages to arrive to process. Those messages could be for mouse movements and clicks, for keyboard typing, for system events, for delegates being available to invoke, etc. So, given a SynchronizationContext instance for the UI thread of a Windows Forms application, to get a delegate to execute on that UI thread, one simply needs to pass it to Post.
The same goes for Windows Presentation Foundation (WPF). It has its own SynchronizationContext-derived type with a Post override that similarly “marshals” a delegate to the UI thread (via Dispatcher.BeginInvoke), in this case managed by a WPF Dispatcher rather than a Windows Forms Control.
And for Windows RunTime (WinRT). It has its own SynchronizationContext-derived type with a Post override that also queues the delegate to the UI thread via its CoreDispatcher.
This goes beyond just “run this delegate on the UI thread”. Anyone can implement a SynchronizationContext with a Post that does anything. For example, I may not care what thread a delegate runs on, but I want to make sure that any delegates Post‘d to my SynchronizationContext are executed with some limited degree of concurrency. I can achieve that with a custom SynchronizationContext like this:
internal sealed class MaxConcurrencySynchronizationContext : SynchronizationContext
{
private readonly SemaphoreSlim _semaphore;
public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel) =>
_semaphore = new SemaphoreSlim(maxConcurrencyLevel);
public override void Post(SendOrPostCallback d, object state) =>
_semaphore.WaitAsync().ContinueWith(delegate
{
try { d(state); } finally { _semaphore.Release(); }
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
public override void Send(SendOrPostCallback d, object state)
{
_semaphore.Wait();
try { d(state); } finally { _semaphore.Release(); }
}
}
In fact, the unit testing framework xunit provides a SynchronizationContext very similar to this, which it uses to limit the amount of code associated with tests that can be run concurrently.
The benefit of all of this is the same as with any abstraction: it provides a single API that can be used to queue a delegate for handling however the creator of the implementation desires, without needing to know the details of that implementation. So, if I’m writing a library, and I want to go off and do some work, and then queue a delegate back to the original location’s “context”, I just need to grab their SynchronizationContext, hold on to it, and then when I’m done with my work, call Post on that context to hand off the delegate I want invoked. I don’t need to know that for Windows Forms I should grab a Control and use its BeginInvoke, or for WPF I should grab a Dispatcher and uses its BeginInvoke, or for xunit I should somehow acquire its context and queue to it; I simply need to grab the current SynchronizationContext and use that later on. To achieve that, SynchronizationContext provides a Current property, such that to achieve the aforementioned objective I might write code like this:
public void DoWork(Action worker, Action completion)
{
SynchronizationContext sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
try { worker(); }
finally { sc.Post(_ => completion(), null); }
});
}
A framework that wants to expose a custom context from Current uses the SynchronizationContext.SetSynchronizationContext method.
What is a TaskScheduler?
SynchronizationContext is a general abstraction for a “scheduler”. Individual frameworks sometimes have their own abstractions for a scheduler, and System.Threading.Tasks is no exception. When Tasks are backed by a delegate such that they can be queued and executed, they’re associated with a System.Threading.Tasks.TaskScheduler. Just as SynchronizationContext provides a virtual Post method to queue a delegate’s invocation (with the implementation later invoking the delegate via typical delegate invocation mechanisms), TaskScheduler provides an abstract QueueTask method (with the implementation later invoking that Task via the ExecuteTask method).
The default scheduler as returned by TaskScheduler.Default is the thread pool, but it’s possible to derive from TaskScheduler and override the relevant methods to achieve arbitrary behaviors for when and where a Task is invoked. For example, the core libraries include the System.Threading.Tasks.ConcurrentExclusiveSchedulerPair type. An instance of this class exposes two TaskScheduler properties, one called ExclusiveScheduler and one called ConcurrentScheduler. Tasks scheduled to the ConcurrentScheduler may run concurrently, but subject to a limit supplied to ConcurrentExclusiveSchedulerPair when it was constructed (similar to the MaxConcurrencySynchronizationContext shown earlier), and no ConcurrentScheduler Tasks will run when a Task scheduled to ExclusiveScheduler is running, with only one exclusive Task allowed to run at a time… in this way, it behaves very much like a reader/writer-lock.
Like SynchronizationContext, TaskScheduler also has a Current property, which returns the “current” TaskScheduler. Unlike SynchronizationContext, however, there’s no method for setting the current scheduler. Instead, the current scheduler is the one associated with the currently running Task, and a scheduler is provided to the system as part of starting a Task. So, for example, this program will output “True”, as the lambda used with StartNew is executed on the ConcurrentExclusiveSchedulerPair‘s ExclusiveScheduler and will see TaskScheduler.Current set to that scheduler:
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() =>
{
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}
Interestingly, TaskScheduler provides a static FromCurrentSynchronizationContext method, which creates a new TaskScheduler that queues Tasks to run on whatever SynchronizationContext.Current returned, using its Post method for queueing tasks.
How do SynchronizationContext and TaskScheduler relate to await?
Consider writing a UI app with a Button. Upon clicking the Button, we want to download some text from a web site and set it as the Button‘s Content. The Button should only be accessed from the UI thread that owns it, so when we’ve successfully downloaded the new date and time text and want to store it back into the Button‘s Content, we need to do so from the thread that owns the control. If we don’t, we get an exception like:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
If we were writing this out manually, we could use SynchronizationContext as shown earlier to marshal the setting of the Content back to the original context, such as via a TaskScheduler:
private static readonly HttpClient s_httpClient = new HttpClient();
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
{
downloadBtn.Content = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
or using SynchronizationContext directly:
private static readonly HttpClient s_httpClient = new HttpClient();
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
SynchronizationContext sc = SynchronizationContext.Current;
s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
{
sc.Post(delegate
{
downloadBtn.Content = downloadTask.Result;
}, null);
});
}
Both of these approaches, though, explicitly uses callbacks. We would instead like to write the code naturally with async/await:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content = text;
}
This “just works”, successfully setting Content on the UI thread, because just as with the manually implemented version above, awaiting a Task pays attention by default to SynchronizationContext.Current, as well as to TaskScheduler.Current. When you await anything in C#, the compiler transforms the code to ask (via calling GetAwaiter) the “awaitable” (in this case, the Task) for an “awaiter” (in this case, a TaskAwaiter
object scheduler = SynchronizationContext.Current;
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
In other words, it first checks whether there’s a SynchronizationContext set, and if there isn’t, whether there’s a non-default TaskScheduler in play. If it finds one, when the callback is ready to be invoked, it’ll use the captured scheduler; otherwise, it’ll generally just execute the callback on as part of the operation completing the awaited task.
What does ConfigureAwait(false) do?
The ConfigureAwait method isn’t special: it’s not recognized in any special way by the compiler or by the runtime. It is simply a method that returns a struct (a ConfiguredTaskAwaitable) that wraps the original task it was called on as well as the specified Boolean value. Remember that await can be used with any type that exposes the right pattern. By returning a different type, it means that when the compiler accesses the instances GetAwaiter method (part of the pattern), it’s doing so off of the type returned from ConfigureAwait rather than off of the task directly, and that provides a hook to change the behavior of how the await behaves via this custom awaiter.
Specifically, awaiting the type returned from ConfigureAwait(continueOnCapturedContext: false) instead of awaiting the Task directly ends up impacting the logic shown earlier for how the target context/scheduler is captured. It effectively makes the previously shown logic more like this:
object scheduler = null;
if (continueOnCapturedContext)
{
scheduler = SynchronizationContext.Current;
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
}
In other words, by specifying false, even if there is a current context or scheduler to call back to, it pretends as if there isn’t.
Why would I want to use ConfigureAwait(false)?
ConfigureAwait(continueOnCapturedContext: false) is used to avoid forcing the callback to be invoked on the original context or scheduler. This has a few benefits:
Improving performance. There is a cost to queueing the callback rather than just invoking it, both because there’s extra work (and typically extra allocation) involved, but also because it means certain optimizations we’d otherwise like to employ in the runtime can’t be used (we can do more optimization when we know exactly how the callback will be invoked, but if it’s handed off to an arbitrary implementation of an abstraction, we can sometimes be limited). For very hot paths, even the extra costs of checking for the current SynchronizationContext and the current TaskScheduler (both of which involve accessing thread statics) can add measurable overhead. If the code after an await doesn’t actually require running in the original context, using ConfigureAwait(false) can avoid all these costs: it won’t need to queue unnecessarily, it can utilize all the optimizations it can muster, and it can avoid the unnecessary thread static accesses.
Avoiding deadlocks. Consider a library method that uses await on the result of some network download. You invoke this method and synchronously block waiting for it to complete, such as by using .Wait() or .Result or .GetAwaiter().GetResult() off of the returned Task object. Now consider what happens if your invocation of it happens when the current SynchronizationContext is one that limits the number of operations that can be running on it to 1, whether explicitly via something like the MaxConcurrencySynchronizationContext shown earlier, or implicitly by this being a context that only has one thread that can be used, e.g. a UI thread. So you invoke the method on that one thread and then block it waiting for the operation to complete. The operation kicks off the network download and awaits it. Since by default awaiting a Task will capture the current SynchronizationContext, it does so, and when the network download completes, it queues back to the SynchronizationContext the callback that will invoke the remainder of the operation. But the only thread that can process the queued callback is currently blocked by your code blocking waiting on the operation to complete. And that operation won’t complete until the callback is processed. Deadlock! This can apply even when the context doesn’t limit the concurrency to just 1, but when the resources are limited in any fashion. Imagine the same situation, except using the MaxConcurrencySynchronizationContext with a limit of 4. And instead of making just one call to the operation, we queue to that context 4 invocations, each of which makes the call and blocks waiting for it to complete. We’ve now still blocked all of the resources while waiting for the async methods to complete, and the only thing that will allow those async methods to complete is if their callbacks can be processed by this context that’s already entirely consumed. Again, deadlock! If instead the library method had used ConfigureAwait(false), it would not queue the callback back to the original context, avoiding the deadlock scenarios.
Why would I want to use ConfigureAwait(true)?
You wouldn’t, unless you were using it purely as an indication that you were purposefully not using ConfigureAwait(false) (e.g. to silence static analysis warnings or the like). ConfigureAwait(true) does nothing meaningful. When comparing await task with await task.ConfigureAwait(true), they’re functionally identical. If you see ConfigureAwait(true) in production code, you can delete it without ill effect.
The ConfigureAwait method accepts a Boolean because there are some niche situations in which you want to pass in a variable to control the configuration. But the 99% use case is with a hardcoded false argument value, ConfigureAwait(false).
When should I use ConfigureAwait(false)?
It depends: are you implementing application-level code or general-purpose library code?
When writing applications, you generally want the default behavior (which is why it is the default behavior). If an app model / environment (e.g. Windows Forms, WPF, ASP.NET Core, etc.) publishes a custom SynchronizationContext, there’s almost certainly a really good reason it does: it’s providing a way for code that cares about synchronization context to interact with the app model / environment appropriately. So if you’re writing an event handler in a Windows Forms app, writing a unit test in xunit, writing code in an ASP.NET MVC controller, whether or not the app model did in fact publish a SynchronizationContext, you want to use that SynchronizationContext if it exists. And that means the default / ConfigureAwait(true). You make simple use of await, and the right things happen with regards to callbacks/continuations being posted back to the original context if one existed. This leads to the general guidance of: if you’re writing app-level code, do not use ConfigureAwait(false). If you think back to the Click event handler code example earlier in this post:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content = text;
}
the setting of downloadBtn.Content = text needs to be done back in the original context. If the code had violated this guideline and instead used ConfigureAwait(false) when it shouldn’t have:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false); // bug
downloadBtn.Content = text;
}
bad behavior will result. The same would go for code in a classic ASP.NET app reliant on HttpContext.Current; using ConfigureAwait(false) and then trying to use HttpContext.Current is likely going to result in problems.
In contrast, general-purpose libraries are “general purpose” in part because they don’t care about the environment in which they’re used. You can use them from a web app or from a client app or from a test, it doesn’t matter, as the library code is agnostic to the app model it might be used in. Being agnostic then also means that it’s not going to be doing anything that needs to interact with the app model in a particular way, e.g. it won’t be accessing UI controls, because a general-purpose library knows nothing about UI controls. Since we then don’t need to be running the code in any particular environment, we can avoid forcing continuations/callbacks back to the original context, and we do that by using ConfigureAwait(false) and gaining both the performance and reliability benefits it brings. This leads to the general guidance of: if you’re writing general-purpose library code, use ConfigureAwait(false). This is why, for example, you’ll see every (or almost every) await in the .NET Core runtime libraries using ConfigureAwait(false) on every await; with a few exceptions, in cases where it doesn’t it’s very likely a bug to be fixed. For example, this PR fixed a missing ConfigureAwait(false) call in HttpClient.
As with all guidance, of course, there can be exceptions, places where it doesn’t make sense. For example, one of the larger exemptions (or at least categories that requires thought) in general-purpose libraries is when those libraries have APIs that take delegates to be invoked. In such cases, the caller of the library is passing potentially app-level code to be invoked by the library, which then effectively renders those “general purpose” assumptions of the library moot. Consider, for example, an asynchronous version of LINQ’s Where method, e.g. public static async IAsyncEnumerable
Even with these special cases, the general guidance stands and is a very good starting point: use ConfigureAwait(false) if you’re writing general-purpose library / app-model-agnostic code, and otherwise don’t.
Does ConfigureAwait(false) guarantee the callback won’t be run in the original context?
No. It guarantees it won’t be queued back to the original context… but that doesn’t mean the code after an await task.ConfigureAwait(false) won’t still run in the original context. That’s because awaits on already-completed awaitables just keep running past the await synchronously rather than forcing anything to be queued back. So, if you await a task that’s already completed by the time it’s awaited, regardless of whether you used ConfigureAwait(false), the code immediately after this will continue to execute on the current thread in whatever context is still current.
Is it ok to use ConfigureAwait(false) only on the first await in my method and not on the rest?
In general, no. See the previous FAQ. 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.
One notable exception to this is if you know that the first await will always complete asynchronously and the thing being awaited will invoke its callback in an environment free of a custom SynchronizationContext or a TaskScheduler. For example, CryptoStream in the .NET runtime libraries wants to ensure that its potentially computationally-intensive code doesn’t run as part of the caller’s synchronous invocation, so it uses a custom awaiter to ensure that everything after the first await runs on a thread pool thread. However, even in that case you’ll notice that the next await still uses ConfigureAwait(false); technically that’s not necessary, but it makes code review a lot easier, as otherwise every time this code is looked at it doesn’t require an analysis to understand why ConfigureAwait(false) was left off.
Can I use Task.Run to avoid using ConfigureAwait(false)?
Yes. If you write:
Task.Run(async delegate
{
await SomethingAsync(); // won't see the original context
});
then a ConfigureAwait(false) on that SomethingAsync() call will be a nop, because the delegate passed to Task.Run is going to be executed on a thread pool thread, with no user code higher on the stack, such that SynchronizationContext.Current will return null. Further, Task.Run implicitly uses TaskScheduler.Default, which means querying TaskScheduler.Current inside of the delegate will also return Default. That means the await will exhibit the same behavior regardless of whether ConfigureAwait(false) was used. It also doesn’t make any guarantees about what code inside of this lambda might do. If you have the code:
Task.Run(async delegate
{
SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
await SomethingAsync(); // will target SomeCoolSyncCtx
});
then the code inside SomethingAsync will in fact see SynchronizationContext.Current as that SomeCoolSyncCtx instance, and both this await and any non-configured awaits inside SomethingAsync will post back to it. So to use this approach, you need to understand what all of the code you’re queueing may or may not do and whether its actions could thwart yours.
This approach also comes at the expense of needing to create/queue an additional task object. That may or may not matter to your app or library depending on your performance sensitivity.
Also keep in mind that such tricks may cause more problems than they’re worth and have other unintended consequences. For example, static analysis tools (e.g. Roslyn analyzers) have been written to flag awaits that don’t use ConfigureAwait(false), such as CA2007. If you enable such an analyzer but then employ a trick like this just to avoid using ConfigureAwait, there’s a good chance the analyzer will flag it, and actually cause more work for you. So maybe you then disable the analyzer because of its noisiness, and now you end up missing other places in the codebase where you actually should have been using ConfigureAwait(false).
Can I use SynchronizationContext.SetSynchronizationContext to avoid using ConfigureAwait(false)?
No. Well, maybe. It depends on the involved code.
Some developers write code like this:
Task t;
SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
t = CallCodeThatUsesAwaitAsync(); // awaits in here won't see the original context
}
finally { SynchronizationContext.SetSynchronizationContext(old); }
await t; // will still target the original context
in hopes that it’ll make the code inside CallCodeThatUsesAwaitAsync see the current context as null. And it will. However, the above will do nothing to affect what the await sees for TaskScheduler.Current, so if this code is running on some custom TaskScheduler, awaits inside CallCodeThatUsesAwaitAsync (and that don’t use ConfigureAwait(false)) will still see and queue back to that custom TaskScheduler.
All of the same caveats also apply as in the previous Task.Run-related FAQ: there are perf implications of such a workaround, and the code inside the try could also thwart these attempts by setting a different context (or invoking code with a non-default TaskScheduler).
With such a pattern, you also need to be careful about a slight variation:
SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
await t;
}
finally { SynchronizationContext.SetSynchronizationContext(old); }
See the problem? It’s a bit hard to see but also potentially very impactful. There’s no guarantee that the await will end up invoking the callback/continuation on the original thread, which means the resetting of the SynchronizationContext back to the original may not actually happen on the original thread, which could lead subsequent work items on that thread to see the wrong context (to counteract this, well-written app models that set a custom context generally add code to manually reset it before invoking any further user code). And even if it does happen to run on the same thread, it may be a while before it does, such that the context won’t be appropriately restored for a while. And if it runs on a different thread, it could end up setting the wrong context onto that thread. And so on. Very far from ideal.
I’m using GetAwaiter().GetResult(). Do I need to use ConfigureAwait(false)?
No. ConfigureAwait only affects the callbacks. Specifically, the awaiter pattern requires awaiters to expose an IsCompleted property, a GetResult method, and an OnCompleted method (optionally with an UnsafeOnCompleted method). ConfigureAwait only affects the behavior of {Unsafe}OnCompleted, so if you’re just directly calling to the awaiter’s GetResult() method, whether you’re doing it on the TaskAwaiter or the ConfiguredTaskAwaitable.ConfiguredTaskAwaiter makes zero behavior difference. So, if you see task.ConfigureAwait(false).GetAwaiter().GetResult() in code, you can replace it with task.GetAwaiter().GetResult() (and also consider whether you really want to be blocking like that).
I know I’m running in an environment that will never have a custom SynchronizationContext or custom TaskScheduler. Can I skip using ConfigureAwait(false)?
Maybe. It depends on how sure you are of the “never” part. As mentioned in previous FAQs, just because the app model you’re working in doesn’t set a custom SynchronizationContext and doesn’t invoke your code on a custom TaskScheduler doesn’t mean that some other user or library code doesn’t. So you need to be sure that’s not the case, or at least recognize the risk if it may be.
I’ve heard ConfigureAwait(false) is no longer necessary in .NET Core. True?
False. It’s needed when running on .NET Core for exactly the same reasons it’s needed when running on .NET Framework. Nothing’s changed in that regard.
What has changed, however, is whether certain environments publish their own SynchronizationContext. In particular, whereas the classic ASP.NET on .NET Framework has its own SynchronizationContext, in contrast ASP.NET Core does not. That means that code running in an ASP.NET Core app by default won’t see a custom SynchronizationContext, which lessens the need for ConfigureAwait(false) running in such an environment.
It doesn’t mean, however, that there will never be a custom SynchronizationContext or TaskScheduler present. If some user code (or other library code your app is using) sets a custom context and calls your code, or invokes your code in a Task scheduled to a custom TaskScheduler, then even in ASP.NET Core your awaits may see a non-default context or scheduler that would lead you to want to use ConfigureAwait(false). Of course, in such situations, if you avoid synchronously blocking (which you should avoid doing in web apps regardless) and if you don’t mind the small performance overheads in such limited occurrences, you can probably get away without using ConfigureAwait(false).
Can I use ConfigureAwait when ‘await foreach’ing an IAsyncEnumerable?
Yes. See this MSDN Magazine article for an example.
await foreach binds to a pattern, and so while it can be used to enumerate an IAsyncEnumerable
Can I use ConfigureAwait when ‘await using’ an IAsyncDisposable?
Yes, though with a minor complication.
As with IAsyncEnumerable
await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false))
{
...
}
The problem here is that the type of c is now not MyAsyncDisposableClass but rather a System.Runtime.CompilerServices.ConfiguredAsyncDisposable, which is the type returned from that ConfigureAwait extension method on IAsyncDisposable.
To get around that, you need to write one extra line:
var c = new MyAsyncDisposableClass();
await using (c.ConfigureAwait(false))
{
...
}
Now the type of c is again the desired MyAsyncDisposableClass. This also has the effect of increasing the scope of c; if that’s impactful, you can wrap the whole thing in braces.
I used ConfigureAwait(false), but my AsyncLocal still flowed to code after the await. Is that a bug?
No, that is expected. AsyncLocal
Could the language help me avoid needing to use ConfigureAwait(false) explicitly in my library?
Library developers sometimes express their frustration with needing to use ConfigureAwait(false) and ask for less invasive alternatives.
Currently there aren’t any, at least not built into the language / compiler / runtime. There are however numerous proposals for what such a solution might look like, e.g. https://github.com/dotnet/csharplang/issues/645, https://github.com/dotnet/csharplang/issues/2542, https://github.com/dotnet/csharplang/issues/2649, and https://github.com/dotnet/csharplang/issues/2746.
If this is important to you, or if you feel like you have new and interesting ideas here, I encourage you to contribute your thoughts to those or new discussions.
Avatar
Stephen Toub
Partner Software Engineer, .NET
Follow
Posted in
.NET
.NET Core
Async
C#
Concurrency
Performance
TPL
Read next
An Introduction to DataFrame
Last month, we announced .NET support for Jupyter notebooks, and showed how to use them to work with .NET for Apache Spark and ML.NET. Today, we're announcing the preview...
Prashanth Govindarajan Prashanth Govindarajan December 16, 2019
49 comments
Collecting and analyzing memory dumps
Introducing a new tool, dotnet-gcdump, for collecting portable memory dumps of .NET Core processes
Avatar Sourabh Shirhatti [MSFT] January 13, 2020
6 comments
51 comments
Comments are closed. Login to edit/delete your existing comments
Page1of comments
Page2of comments
Page3of comments
Next comment
Avatar
Rasťo Novotný December 12, 2019 1:57 amcollapse this comment
Very nice and detailed article.
I have a question about configuring awaiter in method multiple times. Consider following code:
private async Task OnButtonClick()
{
string response1 = await GetSomeData().ConfigureAwait(false);
string response2 = await GetSomeMoreData().ConfigureAwait(true);
_dataTextBox.Text = response1 + response2;
}
Code between GetSomeData() and GetSomeMoreData() does not need to run on UI thread, so it uses ConfigureAwait(false). And then code after GetSomeMoreData() needs to run on UI thread, so it uses ConfigureAwait(true).
However, I assume that GetSomeMoreData() runs already on ThreadPool, so the second CofigureAwait(true) does not capture UI thread context. And therefore remaining code runs on ThreadPool and not on UI thread as required.
So in general I think that the code should not work. But I’ve seen it in some codebases.
Avatar
Thomas Levesque December 12, 2019 2:50 amcollapse this comment
It won’t work in the general case, unless GetSomeData returns synchronously. ConfigureAwait(true) does NOT undo the effect of ConfigureAwait(false). The best thing to do in your scenario is probably to extract the calls to a separate method:
private async Task OnButtonClick()
{
_dataTextBox.Text = await GetDataAsync();
}
private async Task
{
string response1 = await GetSomeData().ConfigureAwait(false);
string response2 = await GetSomeMoreData().ConfigureAwait(false);
return response1 + response2;
}
Avatar
Stephen Toub December 12, 2019 7:23 amcollapse this comment
Thomas is correct, and his recommendation is the right solution.
Avatar
Rasťo Novotný December 12, 2019 8:25 amcollapse this comment
Thank you for confirmation.
Avatar
Eli Arbel December 12, 2019 5:46 amcollapse this comment
Great article 🙂
If perf can be an issue even without a sync context, shouldn’t all of the .NET class libraries have ConfigureAwait(false)? One big example where it’s missing is all of ASP.NET Core. Also it is conceivable for such components to be used from a UI thread, now that .NET Core officially has UI frameworks.
Avatar
Stephen Toub December 12, 2019 7:26 amcollapse this comment
Thanks. I mentioned the thread-static costs for completeness, but the primary cost is when there is a SynchronizationContext / TaskScheduler. The other costs are minor enough that it’s reasonable, even in a high-performance codebase, to choose the readability of not using ConfigureAwait over the potential small gain. (In the name of completeness, there are also some minor costs to use ConfigureAwait, in particular the JIT today isn’t quite as good as we’d like at dealing with the extra layer of structs involved, but that’s getting fixed.)
David Fowler
David FowlerMicrosoft employee December 12, 2019 10:18 amcollapse this comment
That’s correct, most of ASP.NET Core doesn’t use ConfigureAwait(false) and that was an explicit decision because it was deemed unnecessary. There are places where it is used though, like calls to bootstrap ASP.NET Core (using the host) so that scenarios you mention work. If you were to host ASP.NET Core in a WinForms or WPF application, you would end up calling StartAsync from the UI thread and that would do the right thing and use ConfigureAwait(false) internally. Request processing on the other hand is dispatching to the thread pool so unless some other component explicitly set a SynchronizationContext, requests are running on thread pool threads.
Blazor on the other hand does have a SynchronizationContext when running inside of a Blazor component.
Matthew Lieder
Matthew Lieder December 12, 2019 1:59 pmcollapse this comment
Could you help settle a debate my team has after reading this article? Does your strongly emphasized statement “if you’re writing app-level code, do not use ConfigureAwait(false)” apply to webapps (like ASP.NET Core) in addition to desktop GUI apps?
Avatar
Stephen Toub December 12, 2019 4:19 pmcollapse this comment
Yes.
I’m curious about the debate. Can you share each side’s argument? I can try to clarify in the post to avoid future such debates 🙂
Matthew Lieder
Matthew Lieder December 17, 2019 1:21 pmcollapse this comment
Thanks!
The argument for using ConfigureAwait(false) in an ASP.NET Core webapp I believe was based on the logic that it should be used in any code that doesn’t care about the context.
Avatar
Mike-E January 10, 2020 9:54 amcollapse this comment
I believe there are indeed two kinds of application types here, those that are hosted in a server context and those that are hosted on the client. Server-hosted as far as I can tell do not need to capture context. If they do, this (well-written!) article should further consider clarifying why that is the case.
Avatar
Stephen Toub January 13, 2020 9:42 amcollapse this comment
The argument for using ConfigureAwait(false) in an ASP.NET Core webapp I believe was based on the logic that it should be used in any code that doesn’t care about the context.
It should be used in any code that doesn’t care about the context if there might be a context in place. By default in an ASP.NET Core app, there isn’t. The only way there would be one is if some code in your app (either that you wrote directly or via some library you decided to pull in) established one, and in that case it’s the app choosing to do so.
Avatar
Mårten Rånge December 14, 2019 1:13 pmcollapse this comment
I think part of the reason ConfigureAwait still is unclear to developers is the very succinct documentation:
continueOnCapturedContext: true to attempt to marshal the continuation back to the original context captured; otherwise, false.
Note: doesn’t say much about the false case. Any chance this very informative blog post will at least be referenced from the documentation so that developers can find it from the docs?
Avatar
Kostas Vl December 14, 2019 5:13 pmcollapse this comment
How about its generic/unhelpful name, combined with its bool param that maybe should have been an enum? It’s telling that naming the bool does wonders for readability: ConfigureAwait(continueOnCapturedContext: false). Even then it’s still not great, as you have to negate that bool to get the meaningful behaviour.
Why not replace ConfigureAwait(bool) with ContinueAsync()?
Avatar
Andrew Hanlon December 17, 2019 11:16 amcollapse this comment
I think many teams, myself included, use an extension method like NoSync() as a cleaner wrapper for ConfigureAwait(false).
Avatar
Nathan Ferreira December 18, 2019 4:43 amcollapse this comment
We can also use Fody.ConfigureAwait at global level during compilation https://github.com/Fody/ConfigureAwait
Avatar
Evgeny Alekseev December 17, 2019 1:11 pmcollapse this comment
Thank you very much!
Avatar
Tanguy Fautré December 18, 2019 3:58 amcollapse this comment
I cannot stop thinking that Microsoft has chosen the wrong default behaviour here. IMHO ConfigureAwait(false) should have been the default.
In my book, this falls into the C# design mistakes caused by some of its contributor being overly GUI centric. Forcing the majority of the user to go through the pain of undoing the default behaviour for the sake of the minority using WinForms. Other example that pops in my head is ToString(), returning a different format based on the user computer locale; despite the fact that computer almost never talk to humans and very much do talk to other computers where changing the number formatting at random is a disaster.
When programming GUI applications, I’m perfectly aware of the Main UI thread. Designing a threading model around it is “fairly easy” given that the problem is localised to GUI controls and forms. Yet, somehow with async/await, C# has now managed to make it everyone’s problem. Everyone has to type ConfigureAwait(false) everytime they use await/async or risk some random deadlock. Are we really confident this was the best decision?
Also, singletons absolutely everywhere, hiding all the complexity away and giving you a magic black box. It is a well known fact that singletons are the most recommended design (or lack of) when using multithreading (I am being sarcastic here, in case there was any doubt).
I personally avoid await/async like the plague. I have yet to see a developer using them correctly without getting badly burned in complex situations (i.e. not your hello-world WinForms example). The length of this FAQ is a proof of that. Too many details have been obfuscated away from the user, and many of the default behaviours are questionable.
Avatar
Mike-E December 19, 2019 12:19 amcollapse this comment
Feel free to add your upvote and discussion to this very topic here, Tanguy:
https://developercommunity.visualstudio.com/idea/583945/improve-asynchronous-programming-model.html
The length of this FAQ is a proof of that
Nodding… There is no other API in our ecosystem that has gotten so much attention and sheer English words directed towards explaining it than async/await. It’s better than what we had before, but there is certainly the sense that by the sheer volume of research and detail one must assume to understand it that we can do better in a subsequent redress of the problem space.
Avatar
Mike-E January 10, 2020 9:56 amcollapse this comment
👆 Now the 5th-highest requested feature on the .NET Community Feature Request boards. Thank you to all who have voted and have supported it!
Avatar
Stephen Toub January 13, 2020 9:43 amcollapse this comment
Everyone has to type ConfigureAwait(false) everytime they use await/async or risk some random deadlock.
Everyone writing generic, reusable libraries that may be used in various contexts.
That continues to be the minority of C# code written.
Avatar
Xhanti Bomela December 18, 2019 4:07 amcollapse this comment
Great article, thank you for the considered and insightful article.
Avatar
Stephen Toub January 13, 2020 10:12 amcollapse this comment
Thanks.
Avatar
Srinivasan RakhunathanMicrosoft employee December 18, 2019 11:14 pmcollapse this comment
We have a Asp.net core web application but the runtime /target framework is net462. this is an application code and hence as per the guidance, we don’t need to use configureawait false I believe. Is this the right understanding ?
Avatar
Stephen Toub January 13, 2020 9:44 amcollapse this comment
I’m having trouble parsing “We have a Asp.net core web application but the runtime /target framework is net462”. What do you mean by that?
Avatar
Praveen Potturu January 18, 2020 9:01 pmcollapse this comment
I think what he meant to say is that his Asp.net Core web application targets full .net framework rather than .net core.
“That means that code running in an ASP.NET Core app by default won’t see a custom SynchronizationContext, which lessens the need for ConfigureAwait(false) running in such an environment.”
Is this also true in this case?
Avatar
Jozef Izso December 20, 2019 1:52 amcollapse this comment
ConfigureAwait() is the worst possible design. It forces us to write it everywhere without getting guidance if it is needed or not.
The default behavior is broken, as we have to put .ConfigureAwait(false) all around the code, yet the meaning of this call must be studied in several documentations and blog posts.
Avatar
Heku December 21, 2019 3:14 amcollapse this comment
Awesome.
If a method have only one await statement and its return is also the method’s return. In case below, are A/B/C same from their function perspective?
public async Task
public async Task
{
// ...
var result = await DoWork();
return result;
}
public async Task
{
// ...
var result = await DoWork().ConfigureAwait(false);
return result;
}
public Task
{
// ...
return DoWork();
}
Avatar
Stephen Toub January 13, 2020 9:48 amcollapse this comment
A and B are different functionally. When DoWork completes, it needs to invoke the remainder of the method: in A that may queue to the current SynchronizationContext or TaskScheduler, whereas in B it won’t and will likely just invoke the remainder of the method synchronously as part of DoWork’s completion.
A/B and C are also different functionally. First, there’s a difference in exceptional behavior: if DoWork throws an exception, in A/B that exception will end up being stored into the returned Task, whereas in C, that exception will emerge synchronously from the call to C. Second, it’s difficult to say what the callback behavior of C is, because that will depend on how the result of C is being consumed.