Reputation: 148524
I read this article about Task.ConfigureAwait
which can help to prevent deadlocks in async code.
Looking at this code: (I know I shouldn't do .Result
, But it's a part of the question)
private void Button_Click(object sender, RoutedEventArgs e)
{
string result = GetPageStatus().Result;
Textbox.Text = result;
}
public async Task<string> GetPageStatus()
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync("http://www.google.com");
return response.StatusCode.ToString();
}
}
This will result a deadlock because :
The .Result
- operation will then block the current thread (i.e the UI thread) while it waits for the async operation to complete.
Once the network call is complete it will attempt to continue executing the response.StatusCode.ToString()
- method on the captured context. (which is blocked - hence deadlock).
One solution was to use :
var response = await httpClient.GetAsync("http://www.google.com").ConfigureAwait(false);
But other solution was to async all the way ( without blocking) :
/*1*/ private async void Button_Click(object sender, RoutedEventArgs e)
/*2*/ {
/*3*/ string result = await GetPageStatus();
/*4*/ Textbox.Text = result;
/*5*/ }
/*6*/ public async Task<string> GetPageStatus()
/*7*/ {
/*8*/ using (var httpClient = new HttpClient())
/*9*/ {
/*10*/ var response = await httpClient.GetAsync("http://www.google.com");
/*11*/ return response.StatusCode.ToString();
/*12*/ }
/*13*/ }
Question :
(I'm trying to understand how this code helps to solve the problem - via context POV).
Does line #3
and line #10
captures different contexts?
Am I right regarding the way of flow as I think it is:
line #3 calls #6 (which calls #10) and sees that it didn't finish yet, so it awaits ( captured context for #3 = UI thread).
Later on, line #10 capture another context (I will call it newContext) after it finishes, it is back to "newContext" and then releases the UI context(thread).
Was I right?
Upvotes: 6
Views: 516
Reputation: 116538
There are no different contexts. In both cases the SyncrhonizationContext
is the single-threaded UI synchronization context (as long as you're not using ConfigureAwait(false)
)
The deadlock happens when the UI thread is synchronously waiting for itself. You can solve that by either avoiding the UI thread with ConfigureAwait(false)
or not blocking it to begin with by avoiding Task.Result
.
The reason "going async
all the way" solves the deadlock is that the UI thread is no longer blocked and free to run the continuationד of both async
operations.
So:
SyncrhonizationContext
.#11
would resume on the UI thread (which is not blocked) after the GetAsync
task completed which in turn would complete the GetPageStatus
task. Then line #4
would resume on the UI thread as well.It's important to understand that the SynchronizationContext
in this case only makes sure some work would be done by the dedicated thread (serially). As long as the thread is free to execute that work unit a deadlock can never occur.
Upvotes: 3
Reputation: 149538
Does line #3 and line #10 captures different contexts ?
As the way your code looks, no. They both will capture the same UI synchronization context, as you don't use ConfigureAwait(false)
which would prevent the marshaling of the continuation back to the UI context.
Am I right regarding the way of flow as I think it is :
line #3 calls #6 (which calls #10) and sees that it didnt finish yet , so it awaits ( captured context for #3= UI thread).
Later on , line #10 capture another context ( I will call it newContext) after it finishes , it is back to "newContext" and then releases the UI context(thread).
Almost. There is no "new context" being created in your call. It is always the same UI synchronization context. If, for example, you had two async calls one after the other, when one used ConfigureAwait(false)
, the second call would continue its execution on a thread pool thread.
As for your visualization, it does capture properly the execution flow of your code.
Upvotes: 4
Reputation: 33381
I assume that this is WinForms/WPF/Asp.NET
application so in line #3 the UI
thread synchronization context will be captured and task will run in thread pool thread. So the line #10 will be called from thread pool thread and the default scheduler will be use.
Upvotes: 0