4

I can't figure out why Task.Delay(1).Wait() does not block the UI thread when called directly, but does when wrapped inside another Task that is then syncrhonously waited on.

Take for example this simple WPF window:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DoesNotDeadlock().Wait(); // <-- continues just fine
        CausesDeadlock().Wait(); // <-- deadlocks
    }

    private Task DoesNotDeadlock()
        => Task.Delay(1);

    private async Task CausesDeadlock()
        =>  await Task.Delay(1);
}

Why is the behavior different if we wait for the Delay Task directly vs if we wait for the Task that wraps it?

6
  • 4
    I hope this is just for academic knowledge. You shouldn't be using .Wait() anyway so I hope this is not a quest to figure out how to safely use it. You shouldn't, .Wait() and .Result should be reserved for task-related libraries and only in those cases. Commented Apr 30, 2021 at 8:06
  • 3
    The implementation for await for a Windows program does the equivalent of calling Control.Invoke() when the await is reached in CausesDeadlock(). But because your window is blocked in the CausesDeadlock().Wait() it is unable to process the message sent by Control.Invoke() because the message processing loop is not active, and therefore it locks up. Commented Apr 30, 2021 at 8:16
  • @MatthewWatson yeah, that's a good explanation of the deadlock, the main thread is waiting for a task, but part of the task is the continuation that got scheduled, so that makes sense. What I don't understand is why the same deadlock does not occur when dalling Task.Delay(1).Wait() directly. Commented Apr 30, 2021 at 8:40
  • 2
    Task DoesNotDeadlock executes completely synchronously, with no async machinery. Thus, no potential for deadlock. Task CausesDeadlock uses async machinery because the inner await has an incomplete Task to return. Thus, it deadlocks as per blog.stephencleary.com/2012/07/dont-block-on-async-code.html. Commented Apr 30, 2021 at 9:02
  • 1
    It's not the task that executes asynchronously, it's the await operator that causes it to happen (and only when needed). By creating, returning and storing an instance of Task, you invoke no async machinery yet. It's only after you await something the async part will happen, provided the Task in question is not already complete. Thus, DoesNotDeadlock is completely synchronous, and CausesDeadlock is not because it has an incomplete await inside it. Commented Apr 30, 2021 at 10:13

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.