将async/await与Result混合
本文关键字:Result 混合 await async | 更新日期: 2023-09-27 17:59:03
让我在这个问题的前面介绍几件事:
- 我读过几个SO问题,说你应该而不是这样做(比如如何安全地混合同步和异步代码)
- 我又读过Async/Await-异步编程中的最佳实践,说你不应该这样做
所以我知道这不是最好的做法,不需要任何人告诉我。这更像是一个"为什么这样做"的问题。
解决了这个问题,我的问题是:
我已经编写了一个小型GUI应用程序,它有2个按钮和一个状态标签。其中一个按钮将100%通过同步和异步来重现死锁问题。另一个按钮调用相同的异步方法,但它被封装在Task中,这个方法有效。我知道这不是一个好的编码实践,但我想了解为什么它没有同样的死锁问题。这是代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async Task<string> 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
来观察差异。
我在博客文章Don't Block on Async Code中解释了死锁场景背后的细节。
- 把白板改成"跑步"——好吧,你就这么做了
- 把你的闹钟调晚一个小时。好吧,你这样做
- 制作一张新纸,上面写着"当警报响起时,在白板上写下"完成"一词。把它放在你的收件箱里。你这样做
- 在白板上写下"完成"之前,不要做任何其他事情
- 回到办公桌,等待下一个任务到达收件箱
此工作流阻止您完成工作,因为最后两个步骤的顺序错误。
场景二:你坐在办公桌前。有一个收件箱。它是空的。你的收件箱里突然收到一张描述任务的纸。你跳起来,开始跑来跑去完成任务。但任务是什么?它说要做以下事情:
- 把白板改成"跑步"——好吧,你就这么做了
- 把另一张纸给隔壁隔间的黛比。好吧,你这样做
- 在有人告诉你子任务已经完成之前不要做任何事情
- 当这种情况发生时,在白板上写下"完成"一词
- 回到你的办公桌上
你给黛比的那张纸上写着什么?上面写着:
- 把你的闹钟调晚一个小时。好吧,她就是这么做的
- 当闹钟响起时,在收件箱里放一张纸,告诉Middas你已经结束了
这个工作流程仍然很糟糕,因为(1)你坐在那里什么都不做,而你在等待黛比的闹钟响;(2)你浪费了两个工人的时间,而你本可以让一个工人来做所有的工作。工人价格昂贵。
但此工作流不会阻止您最终完成工作。它不会陷入僵局,因为你不是在等待你自己未来要做的工作,而是在等待其他人来做这项工作。
(我注意到,这与程序中发生的事情并不完全相似,但它足够接近,可以让人理解这个想法。)