Expectations and Dead End Prevention - Clarifications?

I read this article about Task.ConfigureAwait , which can help prevent deadlocks in asynchronous code.

Looking at this code: ( I know I should not do .Result , but this is 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 lead to a deadlock because:

  • Operation .Result - will block the current thread (i.e. the user interface thread) while it waits for the async operation to complete.

  • Once the network call is completed, it will try to continue execution of the response.StatusCode.ToString() method in the captured context. (which is blocked, therefore, a dead end).

One solution was to use:

var response = await httpClient.GetAsync("http://www.google.com").ConfigureAwait(false);

But another solution was completely asynchronous (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 solve the problem - through contextual POV).

  • Does line #3 and line #10 capture different contexts?

  • I am right about the flow method as I think:

    • line # 3 calls # 6 (which calls # 10) and sees that it has not finished yet, so it waits (captured context for # 3 = UI thread).

    • Later, line # 10 will capture a different context (I will call it newContext) after it is completed, it returns to โ€œnewContextโ€ and then frees the user interface context (stream).

Was I right?

  1. Visualization: (is this the correct thread?)

Code execution flow

+6
source share
3 answers

There are no different contexts. In both cases, the SyncrhonizationContext is a single-threaded UI synchronization context (unless you use ConfigureAwait(false) )

Deadlock occurs when a UI thread synchronously waits for itself . You can solve this by either avoiding the UI thread with ConfigureAwait(false) or not blocking it, primarily by avoiding Task.Result .

The reason "go async all the way" resolves the deadlock is that the user interface thread is no longer blocked and cannot start a continuation ื“ of both async operations.

So:

  • No, it's the same SyncrhonizationContext .
  • No, line #11 resume in the user interface thread (which is not blocked) after the GetAsync task GetAsync , which in turn completes the GetPageStatus task. Then line #4 also resume in the user interface thread.

It is important to understand that the SynchronizationContext in this case only ensures that certain work will be performed by a dedicated thread (sequentially). As long as the thread can execute this work block, a deadlock can never happen.

+3
source

Does line # 3 and line # 10 capture different contexts?

What your code looks like, no. Both of them will capture the same UI synchronization context, since you are not using ConfigureAwait(false) , which will prevent marshaling from continuing to return to the UI context.

I am right about the flow method as I think:

line # 3 calls # 6 (which calls # 10) and sees that it hasn't finished yet, so it waits (captured context for # 3 = UI thread).

Subsequently, line # 10 captures a different context (I will call it newContext) after it ends, it returns to "newContext", and then frees the user interface context (stream).

Nearly. Your call does not create a "new context". It is always the same user interface synchronization context. If, for example, you had two asynchronous calls one after the other, when one used ConfigureAwait(false) , the second call continued execution in the thread pool thread.

As for your visualization, it correctly captures the execution flow of your code.

+4
source

I assume this is a WinForms/WPF/Asp.NET , so the line synchronization UI threads will be fixed on line # 3 and the task will be launched in the thread pool thread. This way, line # 10 will be called from the thread pool thread and the default scheduler will be used.

0
source

Source: https://habr.com/ru/post/980126/


All Articles