Royi Namir
Royi Namir

Reputation: 148524

await and deadlock prevention - clarification?

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 :

  1. The .Result - operation will then block the current thread (i.e the UI thread) while it waits for the async operation to complete.

  2. 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).

  1. Does line #3 and line #10 captures different contexts?

  2. 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?

  1. Visualization: (is this the right flow?)

Code execution flow

Upvotes: 6

Views: 516

Answers (3)

i3arnon
i3arnon

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:

  1. No, It's the same SyncrhonizationContext.
  2. No, line #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

Yuval Itzchakov
Yuval Itzchakov

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

Hamlet Hakobyan
Hamlet Hakobyan

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

Related Questions