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

avh*_*hhh 2 c# multithreading asynchronous async-await configureawait

我最近阅读了 Stephen Cleary 的文章,内容涉及当我们在同步方法中调用异步代码时可能发生的死锁: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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

澄清:执行在什么时候离开 UI 线程/上下文并需要返回?有很多关于需要返回的讨论,但从来没有讨论过它何时离开线程/上下文。

Ste*_*ary 5

我要问的是,如果从 Button1_Click 调用 GetJsonAsync 时没有创建一个新线程供其执行,那么 GetJsonAsync 在哪里执行。在 GetJsonAsync 中等待之前,它不是仍在 UI 上下文中执行 Console.WriteLine(...) 吗?

我建议阅读我的async简介。总之:

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

public void Button1_Click(...)
{
  var jsonTask = GetJsonAsync(...);
  textBox1.Text = jsonTask.Result;
}
Run Code Online (Sandbox Code Playgroud)

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

此时await事情可能会变得异步。该任务尚未完成(即客户端尚未收到该字符串),因此向其调用者GetJsonAsync 返回一个未完成的任务。然后Button1_Click阻塞 UI 线程,等待该任务完成(通过调用.Result)。

因此,当前状态GetJsonAsync不再在 UI 线程上运行。它实际上并没有在任何地方“运行”

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