I am learning async/await and after I read this article Don't Block on Async Code
and this Is async/await suitable for methods that are both IO and CPU bound
I notice one Tip from @Stephen Cleary 's article.
Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).
It appeared again in the code of the post as I have attached above.
public async Task<HtmlDocument> LoadPage(Uri address) { using (var httpResponse = await new HttpClient().GetAsync(address) .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound using (var responseContent = httpResponse.Content) using (var contentStream = await responseContent.ReadAsStreamAsync() .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound return LoadHtmlDocument(contentStream); //CPU-bound }
As my knowledge when we using ConfigureAwait(false) the rest of async method will be run in the thread pool. Why we need to add it into every await in transitive closure? I myself just think this is the correct version as what I knew.
public async Task<HtmlDocument> LoadPage(Uri address) { using (var httpResponse = await new HttpClient().GetAsync(address) .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound using (var responseContent = httpResponse.Content) using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound return LoadHtmlDocument(contentStream); //CPU-bound }
It means the second use of ConfigureAwait(false) in using block is useless. Please tell me the correct way. Thanks in advance.
As a general rule, every piece of code that is not in a view model and/or that does not need to go back on the main thread should use ConfigureAwait false. This is simple, easy and can improve the performance of an application by freeing the UI thread for a little longer.
As a general rule, ConfigureAwait(false) should be used for every await unless the method needs its context. 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.
In this video we answer the ever popular question “Which do I use, ConfigureAwait True or False?”. The direct answer to this question is: – If you are a writing code for the UI, use ConfigureAwait(true).
ConfigureAwait in Action You capture the current context before awaiting the task, leaving it to the task context, then recovering (re-entering) it back when the task completes.
As my knowledge when we using
ConfigureAwait(false)
the rest of async method will be run in the thread pool.
Close, but there is an important caveat you are missing. When you resume after awaiting a task with ConfigureAwait(false)
, you will resume on an arbitrary thread. Take note of the words "when you resume."
Let me show you something:
public async Task<string> GetValueAsync() { return "Cached Value"; } public async Task Example1() { await this.GetValueAsync().ConfigureAwait(false); }
Consider the await
in Example1
. Although you are awaiting an async
method, that method does not actually perform any asynchronous work. If an async
method doesn't await
anything, it executes synchronously, and the awaiter never resumes because it never suspended in the first place. As this example shows, calls to ConfigureAwait(false)
may be superfluous: they may have no effect at all. In this example, whatever context you were on when you enter Example1
is the context you will be on after the await
.
Not quite what you expected, right? And yet, it's not altogether unusual. Many async
methods may contain fast paths that don't require the caller to suspend. The availability of a cached resource is a good example (thanks, @jakub-dąbek!), but there plenty of other reasons an async
method might bail early. We often check for various conditions at the beginning of a method to see if we can avoid doing unnecessary work, and async
methods are no different.
Let's look at another example, this time from a WPF application:
async Task DoSomethingBenignAsync() { await Task.Yield(); } Task DoSomethingUnexpectedAsync() { var tcs = new TaskCompletionSource<string>(); Dispatcher.BeginInvoke(Action(() => tcs.SetResult("Done!"))); return tcs.Task; } async Task Example2() { await DoSomethingBenignAsync().ConfigureAwait(false); await DoSomethingUnexpectedAsync(); }
Take a look at Example2
. The first method we await
always runs asynchronously. By the time we hit the second await
, we know we're running on a thread pool thread, so there's no need for ConfigureAwait(false)
on the second call, right? Wrong. Despite having Async
in the name and returning a Task
, our second method wasn't written using async
and await
. Instead, it performs its own scheduling and uses a TaskCompletionSource
to communicate the result. When you resume from your await
, you might[1] end up running on whatever thread provided the result, which in this case is WPF's dispatcher thread. Whoops.
The key takeaway here is that you often don't know exactly what an 'awaitable' method does. With or without ConfigureAwait
, you might end up running somewhere unexpected. This can happen at any level of an async
call stack, so the surest way to avoid inadvertently taking ownership of a single-threaded context is to use ConfigureAwait(false)
with every await
, i.e., throughout the transitive closure.
Of course, there may be times when you want to resume on your current context, and that's fine. That is ostensibly why it's the default behavior. But if you don't genuinely need it, then I recommend using ConfigureAwait(false)
by default. This is especially true for library code. Library code can get called from anywhere, so it's best adhere to the principle of least surprise. That means not locking other threads out of your caller's context when you don't need it. Even if you use ConfigureAwait(false)
everywhere in your library code, your caller will still have the option to resume on their original context if that's what they want.
[1] This behavior may vary by framework and compiler version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With