如果异步调用不一定在不同的线程上执行,阻塞异步调用如何导致死锁?
Posted
技术标签:
【中文标题】如果异步调用不一定在不同的线程上执行,阻塞异步调用如何导致死锁?【英文标题】:How can blocking async calls cause a deadlock if async calls aren't necessarily executed on a different thread? 【发布时间】:2021-02-05 01:21:03 【问题描述】:我最近在此处阅读了 Stephen Cleary 的帖子,内容是我们在此处以同步方法调用异步代码时可能发生的死锁:https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
关于此处稍作修改的示例(我添加的只是写行代码):
// 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;
他的解释是***方法阻塞了 UI 线程等待 GetJsonAsync
完成,而 GetJsonAsync
正在等待 UI 线程被释放以便它可以完成执行。
我的问题是,GetJsonAsync
不是已经在 UI 线程上吗?为什么它需要等待它被释放?根据这篇文章here,调用异步函数不一定要为要执行的方法创建另一个线程。那么如果 GetJsonAsync
一直在 UI 线程上执行,它会如何导致 UI 线程出现问题呢?就像Console.WriteLine()
执行时一样,如果不在 UI 线程上,这在哪里完成?我觉得我在这里遗漏了一些东西,但不知道是什么。
澄清:执行在什么时候离开 UI 线程/上下文并需要返回?有很多关于需要返回的讨论,但从来没有离开线程/上下文。
【问题讨论】:
Stephen 的解释是针对原始代码,而不是针对您修改后的示例 您添加了ConfigureAwait(false)
,这实际上是修复的一半,而不是导致阻塞的完整代码。您正在将原始作者的结论应用于修改后的代码,同时给人以他编写的印象。两者都没有帮助。用一个新的例子可能会更好。
原文章很好的解释了这个问题。不知道如何改进这里
@MickyD 我不确定您看到的是什么,但在修改后的示例中它清楚地表示ConfigureAwait(true)
,这在他编写 await 调用时暗示了这一点。我只是想更具体一点。
@avhhh,GetJsonAsync 在 UI 线程上执行,直到第一个 await
,然后将 GetJsonAsync 的上下文保存到状态机并添加到消息循环中。消息循环将验证“每个循环”上的所有状态机,完成后会将其上下文加载到 UI 线程并继续在 UI 线程上执行。
【参考方案1】:
我要问的是,如果从 Button1_Click 调用 GetJsonAsync 时,如果此调用没有创建一个新线程供其执行,那么 GetJsonAsync 在哪里执行。在 GetJsonAsync 中的 await 之前,它不是还在 UI 上下文中执行 Console.WriteLine(...) 吗?
我建议阅读我的async
intro。总结:
每个异步方法都开始同步执行。这段代码:
public void Button1_Click(...)
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
在 UI 线程上调用 GetJsonAsync
,它确实开始在 UI 线程上执行。它在 UI 线程上执行 Console.WriteLine
,new
s 在 UI 线程上执行客户端,甚至在 UI 线程上调用 GetStringAsync
。它从该方法中获取任务,然后await
s 它(为简单起见,我忽略了ConfigureAwait(true)
)。
await
是事情可能变得异步的点。任务未完成(即客户端尚未收到字符串),因此GetJsonAsync
返回一个未完成的任务给它的调用者。然后Button1_Click
阻塞UI 线程,等待该任务完成(通过调用.Result
)。
因此,当前状态为GetJsonAsync
不再在 UI 线程上运行。是not actually "running" anywhere。
稍后,当该字符串结果到达时,从GetStringAsync
返回的任务完成,GetJsonAsync
需要继续执行。它还没有在 UI 线程上;目前它不在任何地方。由于await
捕获了一个 UI 上下文,它将尝试在该上下文(在 UI 线程上)上恢复。
【讨论】:
这里特别解释:“所以,当前状态是 GetJsonAsync 不再在 UI 线程上运行。它实际上并没有在任何地方“运行”。是我正在寻找的确切答案。谢谢!以上是关于如果异步调用不一定在不同的线程上执行,阻塞异步调用如何导致死锁?的主要内容,如果未能解决你的问题,请参考以下文章