一行异步方法 SynchronizationContext

One-line async method SynchronizationContext

我准备了 WinForms 应用程序来测试单行异步方法是否会导致死锁。 button1_Click 事件等待 GetZero 单行异步代理方法等待的任务。但是,它会导致死锁。为什么?我读到单行异步方法在 await 完成后不需要继续任何操作,因此没有委托 post 到消息泵导致死锁。

作为参考,button2_Click 事件在没有代理调用者的情况下等待任务 GetZero 的结果,并且应用程序运行正常。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var zero = ProxyCallery().Result;
        label1.Text += $"{zero}";
    }

    private void button2_Click(object sender, EventArgs e)
    {
        var zero = GetZero().Result;
        label1.Text += $"{zero}";
    }

    private async Task<int> ProxyCallery()
    {
        return await GetZero();
    }

    private async Task<int> GetZero()
    {
        await Task.Delay(100).ConfigureAwait(false);

        return await Task.FromResult(0);
    }
}

为什么 button1_Click 导致死锁?

await Task.Delay(100).ConfigureAwait(false); 仅为该调用配置等待。它不会影响可能依赖于特定等待的等待,例如 ProxyCallery() 方法中的 await GetZero()

后一个 await 仍然需要在 UI 线程中继续,您已用 ProxyCallery().Result 阻止了该线程。因此陷入僵局。

I've read that one-line async method do not need to continue anything after await completes, so there is no delegate to post to message pump causing deadlock.

我不知道你在哪里读到的,但这是错误的。编译器不会尝试优化 "tail awaits"。实际上,即使方法中的最后一个东西是 await,仍然有代码要继续执行。至少,解包任何异常,而且还将继续传播到 async 方法所代表的 Task

因此,与在其他任何地方找到的 await 语句相比,结束方法的 await 语句在死锁可能性或异步执行的任何其他方面没有任何区别一种方法。

你的 ProxyCallery 实际上是两行,在某种意义上说 异步操作后的延续:

private async Task<int> ProxyCallery()
{
    var zero = await GetZero();
    return zero; // <-- continuation!
}

继续return任务的结果!

目前,上述延续与 Task.Delay 任务的延续不在同一个同步上下文中,这就是导致死锁的原因。

您创建的每个任务都应该在同一个同步上下文中!

private async Task<int> ProxyCallery()
{
    var zero = await GetZero().ConfigureAwait(false);
    return zero;
}