将async/await与Result混合

本文关键字:Result 混合 await async | 更新日期: 2023-09-27 17:59:03

让我在这个问题的前面介绍几件事:

  1. 我读过几个SO问题,说你应该而不是这样做(比如如何安全地混合同步和异步代码)
  2. 我又读过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;
    }
}

将async/await与Result混合

它之所以有效,是因为buttonWorking_Click异步代码(DelayAsync以及传递给Task.Runasync lambda)没有当前的SynchronizationContext,而buttonDeadlock_Click异步代码(DelayAsync)有。您可以通过在调试器中运行并观看SynchronizationContext.Current来观察差异。

我在博客文章Don't Block on Async Code中解释了死锁场景背后的细节。

场景一:你坐在办公桌前。有一个收件箱。它是空的。你的收件箱里突然收到一张描述任务的纸。你跳起来,开始跑来跑去完成任务。但任务是什么?它说要做以下事情:
  • 把白板改成"跑步"——好吧,你就这么做了
  • 把你的闹钟调晚一个小时。好吧,你这样做
  • 制作一张新纸,上面写着"当警报响起时,在白板上写下"完成"一词。把它放在你的收件箱里。你这样做
  • 在白板上写下"完成"之前,不要做任何其他事情
  • 回到办公桌,等待下一个任务到达收件箱

此工作流阻止您完成工作,因为最后两个步骤的顺序错误。

场景二:你坐在办公桌前。有一个收件箱。它是空的。你的收件箱里突然收到一张描述任务的纸。你跳起来,开始跑来跑去完成任务。但任务是什么?它说要做以下事情:

  • 把白板改成"跑步"——好吧,你就这么做了
  • 把另一张纸给隔壁隔间的黛比。好吧,你这样做
  • 在有人告诉你子任务已经完成之前不要做任何事情
  • 当这种情况发生时,在白板上写下"完成"一词
  • 回到你的办公桌上

你给黛比的那张纸上写着什么?上面写着:

  • 把你的闹钟调晚一个小时。好吧,她就是这么做的
  • 当闹钟响起时,在收件箱里放一张纸,告诉Middas你已经结束了

这个工作流程仍然很糟糕,因为(1)你坐在那里什么都不做,而你在等待黛比的闹钟响;(2)你浪费了两个工人的时间,而你本可以让一个工人来做所有的工作。工人价格昂贵。

但此工作流不会阻止您最终完成工作。它不会陷入僵局,因为你不是在等待你自己未来要做的工作,而是在等待其他人来做这项工作。

(我注意到,这与程序中发生的事情并不完全相似,但它足够接近,可以让人理解这个想法。)