6

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

PiotrWolkowski
  • 8,408
  • 6
  • 48
  • 68
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 1
    I don't think that 'capturing the context' is the issue here, it's the synchronous `.Result()` that's eliminated in the 2nd version. – H H Dec 24 '14 at 12:32
  • 1
    `ConfigureAwait` isn't used for deadlock prevention. Look for calls to `Result` or `Wait()` for the culprit. Use `async void` on your event handler and await the asynchronous method – Panagiotis Kanavos Dec 24 '14 at 12:40
  • @PanagiotisKanavos I just [_wrote what i've read_](http://i.imgur.com/Rm8OpGB.png). This was a particular case. – Royi Namir Dec 24 '14 at 12:41
  • 1
    The link is incorrect. The correct code block is the correct way to execute asynchronous code inside an event handler. `ConfigureAwait` is used in library code when you *don't* know how your code will be called. In this case, you do and you actually *want* continuations to run on the UI thread, because you get rid of all the `Invoke` calls you'd need otherwise – Panagiotis Kanavos Dec 24 '14 at 12:45
  • 3
    The point of ConfigureAwait(false) is that there is **no** context. Just try it with the debugger, look at SynchronizationContext.Current. It will be `null`. Which forces the continuation to run on a threadpool thread and not the UI thread. Underlying call is SynchronizationContext.Post() instead of DispatcherSynchronizationContext.Post(). So no deadlock occurs. – Hans Passant Dec 24 '14 at 13:03
  • The premise of the title of that blog post is wrong. Read the articles in [my `async-await` curation](http://curah.microsoft.com/45553/asyncawait-general) to understand how `async-await` works. – Paulo Morgado Dec 24 '14 at 18:43

3 Answers3

4

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 3
    There's no `ThreadPoolSynchronizationContext`. When `SynchronizationContext.Current` is null it uses the `ThreadPool` – i3arnon Dec 24 '14 at 12:38
  • 1
    You example of _two async calls one after the other_ was really helpful. ( to see that the first one "prepared the ground" for the second one's context) – Royi Namir Dec 24 '14 at 12:39
  • 1
    @l3arnon Shit, i've mixed `ThreadPoolTaskScheduler` and sync context :\. Edited the answer. – Yuval Itzchakov Dec 24 '14 at 12:41
3

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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • If so , When does new Contexts are created ? can you supply an example ?. What about the visualization flow , is it correct ? – Royi Namir Dec 24 '14 at 12:30
  • @RoyiNamir context aren't usually created. There's one for the UI thread (WinForms, WPF) and one for asp.net. All other code uses no synchronization context. – i3arnon Dec 24 '14 at 12:34
  • @l3arnon You could create a custom sync context if you wanted to :) – Yuval Itzchakov Dec 24 '14 at 12:34
  • @RoyiNamir Here's an example for actively setting a different SC: [Await, SynchronizationContext, and Console Apps: Part 2](http://blogs.msdn.com/b/pfxteam/archive/2012/01/21/10259307.aspx) – i3arnon Dec 24 '14 at 12:50
  • @I3arnon Yes I already [know the console problematic](http://stackoverflow.com/questions/17630506/async-at-console-app-in-c) environment with async :-) p.s. once you've sent me a link to a site that shows many redundant await lines of code. do you have the link ? – Royi Namir Dec 24 '14 at 12:52
0

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.

Hamlet Hakobyan
  • 32,965
  • 6
  • 52
  • 68
  • I meant - I didn't create a new thread :-) ( as regarding to : _So the line #10 will be called from thread pool thread and the default scheduler will be use._) – Royi Namir Dec 24 '14 at 12:35
  • 2
    @HamletHakobyan That is incorrect. This call doesn't generate any new threads at all. It will only use an IOCP for a short while before marshaling work back to the UI thread. – Yuval Itzchakov Dec 24 '14 at 12:35
  • @YuvalItzchakov, You are right, the `GetPageStatus()` doesn't start any task. Although `GetAsync()` does. – Hamlet Hakobyan Dec 24 '14 at 12:40
  • GetAsync Returns a Task. but it doesn't create a new thread. inside ( using reflector) , it uses TCS. @YuvalItzchakov - Yuval, I wasn't mistaken , was I ? – Royi Namir Dec 24 '14 at 12:47
  • @RoyiNamir that's because the actual asynchronous operation is carried out by .NET and the OS. Anyway, Tasks don't create threads, they use threads from a pool. In `GetAsync` the IO threadpool is involved – Panagiotis Kanavos Dec 24 '14 at 12:48
  • @RoyiNamir Specifically, `HttpClient` *will* use a threadpool thread because it has some time consuming work it has to execute synchronously, such as DNS resolving. This is a rare corner case. Most async methods shouldn't do this. See [my question](http://stackoverflow.com/questions/24983635/why-is-httpclient-sendasync-using-the-thread-pool-to-run-its-work-shouldnt-it) for more on that. – Yuval Itzchakov Dec 24 '14 at 12:50
  • @PanagiotisKanavos Yes , (Just to make it clearer) , when The IO operation completes, it fetches a thread-pool thread and continue serving the request. ( this is done via completion-ports) – Royi Namir Dec 24 '14 at 12:50