我最近读了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线程上了吗?为什么它需要等待它被释放?根据这篇文章,调用异步函数不一定会为该方法创建另一个线程来执行。那么,如果GetJsonAsync
一直在UI线程上执行,它会如何导致UI线程出现问题呢?就像执行Console.WriteLine()
时,如果不是在UI线程上,这是在哪里完成的?我觉得我在这里错过了什么,但不知道是什么。
澄清:执行在什么时候离开UI线程/上下文并需要返回?有很多关于需要返回的讨论,但当它离开线程/上下文时,就永远不会了。
我要问的是,当从Button1_Click调用GetJsonAsync时,它在哪里执行?如果这个调用没有为它创建一个新的线程来执行。在GetJson异步中等待之前,它不是在执行控制台吗。是否仍在UI上下文中写入行(…)?
我建议阅读我的async
简介。总结:
每个异步方法都开始同步执行。此代码:
public void Button1_Click(...)
{
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
}
调用UI线程上的GetJsonAsync
,并且它确实开始在UI线程上执行。它在UI线程上执行Console.WriteLine
,new
在UI线程中建立客户端,甚至在UI线程调用GetStringAsync
。它从该方法中获取一个任务,然后await
执行它(为了简单起见,我忽略了ConfigureAwait(true)
)。
await
是事物可能变得异步的点。任务尚未完成(即客户端尚未收到字符串),因此GetJsonAsync
向其调用方返回一个未完成的任务。然后Button1_Click
阻塞UI线程,等待该任务完成(通过调用.Result
)。
因此,状态当前为GetJsonAsync
不再在UI线程上运行。它实际上并不是";运行";在任何地方
稍后,当该字符串结果到达时,从GetStringAsync
返回的任务完成,并且GetJsonAsync
需要继续执行。它还没有出现在UI线程上;现在不在任何地方。由于await
捕获了一个UI上下文,它将尝试在该上下文上(在UI线程上)恢复。