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?

anakic
  • 2,746
  • 1
  • 30
  • 32
  • 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. – Lasse V. Karlsen Apr 30 '21 at 08: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. – Matthew Watson Apr 30 '21 at 08: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. – anakic Apr 30 '21 at 08: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 https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html. – GSerg Apr 30 '21 at 09:02
  • @GSerg why would it execute synchronously? I don't see how it can know if the task that it returns will be awaited by the caller or not. – anakic Apr 30 '21 at 10: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. – GSerg Apr 30 '21 at 10:13

0 Answers0