2

I recently read Stephen Cleary's post regarding possible deadlocks that occur when we call async code in synchronous methods here: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

Regarding a slightly modified example here (all I added was a writeline code):

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  Console.WriteLine("Before await");
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri).ConfigureAwait(true);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public void Button1_Click(...)
{
  var jsonTask = GetJsonAsync(...);
  textBox1.Text = jsonTask.Result;
}

His explanation is that the top-level method is blocking the UI thread waiting for GetJsonAsync to finish whereas GetJsonAsync is waiting for the UI thread to be freed so it can finish executing.

My question is, isn't GetJsonAsync already on the UI thread? Why would it need to wait for it to be freed? According to this post here calling async functions don't necessarily create another thread for the method to execute on. So how would it cause an issue with the UI thread if GetJsonAsync was executing on the UI thread the entire time? Like when Console.WriteLine() is executed where is this done, if not on the UI thread? I feel like I'm missing something here but don't know what.

Clarification: At which point does the execution leave the UI thread/context and need to return? There so much discussion about needing to return but never when it leaves the thread/context.

avhhh
  • 229
  • 1
  • 3
  • 11
  • 1
    Stephen's explanation was for the original code, not for your modified example –  Feb 05 '21 at 01:30
  • @MickyD All I added was a Console.WriteLine() statement I don't see how different this is from his example. My question is still the same. – avhhh Feb 05 '21 at 01:32
  • 1
    You added `ConfigureAwait(false)` which is actually half of the fix and not the full code that was causing the block. You are applying the original author's conclusion to modified code whilst giving the impression that he wrote it. Neither is helpful. Probably would have been better with a fresh example. –  Feb 05 '21 at 01:41
  • 3
    The original article explains the problem quite well. Not sure how it can be improved upon here –  Feb 05 '21 at 01:41
  • 1
    @MickyD I'm not sure what you are seeing but it clearly says `ConfigureAwait(true)` in the modified example, which is implied when he wrote the await call. I just wanted to be more specific. – avhhh Feb 05 '21 at 01:44
  • In desktop applications UI thread is running message loop, when message loop check for button click event and found it "Clicked", it will execute button click handler. Inside button click handler when you call first `await` `synchronization context` is created and put into the queue of events message loop need to verify on it's "every loop", but after `await` execution returned back and blocked by `task.Result`, so message loop execution is waiting for task to be complete but task is waiting for message loop to "come here" and continue execution - Deadlock. – Fabio Feb 05 '21 at 01:53
  • @Fabio Not sure if you really answered my question. What I'm asking is, where is `GetJsonAsync` executing when it's called from `Button1_Click` if this call doesn't create a new thread for it to execute on. Before the `await` within `GetJsonAsync`, isn't it executing the `Console.WriteLine(...)` within the UI context still? – avhhh Feb 05 '21 at 02:06
  • Maybe "isn't GetJsonAsync already on the UI thread" is the core of the misunderstanding... After a method finished execution is no longer using any threads... Is that the thing you trying to clarify? – Alexei Levenkov Feb 05 '21 at 02:24
  • @AlexeiLevenkov yes that is essentially what I want to clarify. So `Button1_Click` calls `GetJsonAsync ` within the UI thread, which blocks the UI thread until `GetJsonAsync ` finishes. Where is `GetJsonAsync` executing its contents since the UI thread is already locked? Additionally, where is `GetStringAsync` within `GetJsonAsync` executing? I'm confused on when it leaves the UI thread since the blog states that it needs to return to the UI thread. When did it ever leave? – avhhh Feb 05 '21 at 02:44
  • 1
    @avhhh, GetJsonAsync executed on UI thread until first `await`, then context of GetJsonAsync is saved into state machine and added to the message loop. Message loop will verify all state machines on "every loop" and when complete it will load it's context into UI thread and continue execution on UI thread. – Fabio Feb 05 '21 at 02:46
  • I see, @Fabio so the UI becomes truly "blocked" when the `await client.GetStringAsync(uri).ConfigureAwait(true);` is called. Then is `GetStringAsync` executed on a different thread? – avhhh Feb 05 '21 at 02:56
  • @avhhh, Nothing is executed on different thread. Notice that when thread code accessing external resources(file system, database or web services) the thread is doing nothing but only waiting for a response. Here is the beauty of `async-await` - instead of waiting for a response execution will "saves" current context and continue executing something else. For winforms application message loop will continue to handle other events, on every iteration message loop will check if state machine is complete and when it is complete - it will continue it's execution on the same thread. – Fabio Feb 05 '21 at 03:29
  • @Fabio are you sure that all state machines are checked on every iteration of the message loop? This sounds quite inefficient to me. My understanding is that when the task is finished, the state machine will [`Post`](https://referencesource.microsoft.com/system.windows.forms/winforms/managed/system/winforms/WindowsFormsSynchronizationContext.cs.html#de633159d3cb47c3) a continuation/callback delegate on the captured synchronization context, which in turn will invoke this delegate on the message loop (using the `Control.BeginInvoke` mechanism). – Theodor Zoulias Feb 05 '21 at 12:30
  • 1
    avhhh the `ConfigureAwait(true)` is rarely used, which makes it very easy to be confused with `ConfigureAwait(false)`. So instead of making the example more specific/explicit, you've probably made it more confusing. Regarding your phrase *"`GetJsonAsync` was executing on the UI thread the entire time"*, what do you count as "entire time"? Do you include the duration that the task returned by the `client.GetStringAsync(uri)` method was `await`ed? During that duration [nothing](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) was running on the UI thread, or any other thread. – Theodor Zoulias Feb 05 '21 at 12:55
  • 1
    @TheodorZoulias You are correct. `async` methods resuming on a UI context will do so via `SynchronizationContext`, which (eventually) results in a Win32 message entering the UI thread's queue. The normal Win32 message loop processes messages one at a time, sleeping until more arrive. – Stephen Cleary Feb 05 '21 at 14:02

1 Answers1

5

What I'm asking is, where is GetJsonAsync executing when it's called from Button1_Click if this call doesn't create a new thread for it to execute on. Before the await within GetJsonAsync, isn't it executing the Console.WriteLine(...) within the UI context still?

I recommend reading my async intro. In summary:

Every asynchronous method begins executing synchronously. This code:

public void Button1_Click(...)
{
  var jsonTask = GetJsonAsync(...);
  textBox1.Text = jsonTask.Result;
}

calls GetJsonAsync on the UI thread, and it does begin executing on the UI thread. It executes Console.WriteLine on the UI thread, news up a client on the UI thread, and even calls GetStringAsync on the UI thread. It gets a task back from that method, and then awaits it (I'm ignoring the ConfigureAwait(true) for simplicity).

The await is the point at which things may become asynchronous. The task isn't complete (i.e., the client hasn't received the string yet), so GetJsonAsync returns an incomplete task to its caller. Then Button1_Click blocks the UI thread, waiting for that task to complete (by calling .Result).

So, the state is currently GetJsonAsync is no longer running on the UI thread. It is not actually "running" anywhere.

Later, when that string result arrives, the task that was returned from GetStringAsync is completed, and GetJsonAsync needs to resume executing. It's not already on the UI thread; it's not anywhere at the moment. Since the await captured a UI context, it will attempt to resume on that context (on the UI thread).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • This particular explanation here: "So, the state is currently GetJsonAsync is no longer running on the UI thread. It is not actually "running" anywhere." was the exact answer I was looking for. Thank you! – avhhh Feb 05 '21 at 14:50