如果异步调用不一定在不同的线程上执行,那么阻塞异步调用如何导致死锁?

How can blocking async calls cause a deadlock if async calls aren't necessarily executed on a different thread?

我最近阅读了 Stephen Cleary 的 post 关于我们在此处以同步方法调用异步代码时可能发生的死锁:https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

关于此处稍微修改的示例(我添加的只是一个 writeline 代码):

// 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;
}

他的解释是顶级方法正在阻塞等待 GetJsonAsync 完成的 UI 线程,而 GetJsonAsync 正在等待 UI 线程完成已释放,以便它可以完成执行。

我的问题是,GetJsonAsync 不是已经在 UI 线程上了吗?为什么需要等待它被释放?根据此 post here 调用异步函数不一定会为要执行的方法创建另一个线程。那么,如果 GetJsonAsync 一直在 UI 线程上执行,它会如何导致 UI 线程出现问题?就像执行 Console.WriteLine() 时,如果不在 UI 线程上,这是在哪里完成的?我觉得我在这里错过了什么,但不知道是什么。

澄清:执行在什么时候离开 UI thread/context 并需要 return?有很多关于需要 return 的讨论,但从来没有离开 thread/context.

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?

我建议阅读我的 async intro。总结:

每个异步方法开始同步执行。此代码:

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

在 UI 线程上调用 GetJsonAsync,它确实开始在 UI 线程上执行。它在 UI 线程上执行 Console.WriteLinenew 在 UI 线程上启动客户端,甚至在 UI 线程上调用 GetStringAsync .它从该方法返回一个任务,然后 await 执行它(为简单起见,我忽略了 ConfigureAwait(true))。

await 是事物可能变得异步的点。任务未完成(即,客户端尚未收到字符串),因此 GetJsonAsync returns 对其调用者来说是未完成的任务。然后 Button1_Click 阻塞 UI 线程,等待该任务完成(通过调用 .Result)。

因此,当前状态 GetJsonAsync 不再是 UI 线程上的 运行。是not actually "running" anywhere.

稍后,当该字符串结果到达时,从GetStringAsync 返回的任务完成,GetJsonAsync 需要继续执行。它不在 UI 线程中;它现在不在任何地方。由于 await 捕获了 UI 上下文,它将尝试在该上下文(在 UI 线程上)上恢复。