Search code examples
c#winformsdeadlocksynchronizationcontext

One-line async method SynchronizationContext


I prepared WinForms application to test if a one-line async method would cause a deadlock. button1_Click event waits for GetZero task awaited by one-line async proxy method. However, it causes deadlock. Why? 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.

For reference, button2_Click event waits for the result of task GetZero without proxy caller, and application works fine.

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);
    }
}

Why is button1_Click causing deadlock?


Solution

  • await Task.Delay(100).ConfigureAwait(false); configures the await only for that call. It doesn't affect awaits that might depend on that particular one, such as the await GetZero() in the ProxyCallery() method.

    That latter await is still going to require continuation in the UI thread, which you've blocked with ProxyCallery().Result. Hence the deadlock.

    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.

    I don't know where you've read that, but it's false. The compiler doesn't try to optimize "tail awaits". In reality, even if the last thing in the method is an await, there is still code to be executed on the continuation. At the very least, unwrapping any exception, but also propagating the continuation to the Task represented by that async method.

    So there is no difference at all with respect to deadlock potential, or any other aspect of asynchronous execution, for await statements that conclude a method as compared to await statements found anywhere else in a method.