将async / await与Result混合
让我先简单介绍一下这个问题:
- 我已经阅读了几个SO问题,说你不应该这样做(比如如何安全地混合同步和异步代码 )
- 我已经读过Async / Await – 异步编程中的最佳实践再次说你不应该这样做
所以我知道这不是最佳做法,也不需要任何人告诉我这样做。 这更像是“为什么这个工作”的问题。
有了这个,这是我的问题:
我写了一个小的GUI应用程序,它有2个按钮和一个状态标签。 其中一个按钮将在100%的时间内重现同步和异步的死锁问题。 另一个按钮调用相同的异步方法,但它包含在一个任务中,这个工作。 我知道这不是一个好的编码实践,但我想理解它为什么没有相同的死锁问题。 这是代码:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async Task DelayAsync() { await Task.Delay(1000); return "Done"; } private void buttonDeadlock_Click(object sender, EventArgs e) { labelStatus.Text = "Status: Running"; // causes a deadlock because of mixing sync and async code var result = DelayAsync().Result; // never gets here labelStatus.Text = "Status: " + result; } private void buttonWorking_Click(object sender, EventArgs e) { labelStatus.Text = "Status: Running"; string result = null; // still technically mixes sync and async, but works, why? result = Task.Run(async () => { return await DelayAsync(); }).Result; labelStatus.Text = "Status: " + result; } }
它的工作原理是buttonWorking_Click
异步代码( DelayAsync
以及传递给Task.Run
的async
lambda)没有当前的SynchronizationContext
,而buttonDeadlock_Click
异步代码( DelayAsync
)则没有。 您可以通过在调试器中运行并观察SynchronizationContext.Current
来观察差异。
我在博客文章Do not Block on Async Code中解释了死锁场景背后的细节。
情景一:你坐在办公桌前。 有一个收件箱。 它是空的。 一张纸突然到达收件箱中描述任务。 你跳起来,开始跑来跑去做任务。 但是任务是什么? 它说要做以下事情:
- 更改白板说“正在运行” – 好的,你这样做。
- 将闹钟设置为一小时后。 好的,你做到了。
- 创建一张新纸,上面写着“当闹钟响起时,在白板上写下DONE”。 把它放在你的收件箱里。 你做吧。
- 在DONE写在白板上之前不要做任何其他事情。
- 回到您的办公桌,等待下一个任务到达收件箱。
此工作流程阻止您完成工作,因为最后两个步骤的顺序错误。
情景二:你坐在办公桌前。 有一个收件箱。 它是空的。 一张纸突然到达收件箱中描述任务。 你跳起来,开始跑来跑去做任务。 但是任务是什么? 它说要做以下事情:
- 更改白板说“正在运行” – 好的,你这样做。
- 在下一个小隔间给另一张纸给黛比。 好的,你做到了。
- 在有人告诉您子任务完成之前不要做任何事情。
- 发生这种情况时,请在白板上写下DONE字样。
- 回到你的办公桌。
你黛比说的这张纸怎么样? 它说:
- 将闹钟设置为一小时后。 好的,她这样做。
- 当闹钟响起时,在收件箱里放一张纸说要告诉Middas你已经完成了。
这个工作流程仍然很糟糕 (1)你在等待黛比的闹钟熄火时坐在那里什么都不做,(2)当你可以让一个工人完成所有的工作时,你浪费了两个工人的时间。 工人很贵。
但是这个工作流程并不能阻止你最终完成工作。 它不会陷入僵局,因为你没有等待将来你将要做的工作,你正在等待别人去做这项工作。
(我注意到这不是你的程序中发生的事情的完全类比,但它足够接近这个想法。)