Search code examples
c#wpfasynchronousasync-await

Call of asynchronous function in a void method on UI thread (WPF)


I have the following problem: I need to bind an asynchronous method to the Window.Closing event. Since the Closing event expects a CancelEventHandler with void signature, my WindowClosing method can't be of type Task. So I have this:

public void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    DeinitializeThisFirst();
    DeinitializeThisAfterwards();
}

For hardware reasons, DeinitializeThisFirst() must finish before DeinitializeThisAfterwards() is called. But DeinitializeThisFirst() is an awaitable Task. Since I can't change the signature of Window_Closing to be a Task, I can't do this:

public async Task Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    await DeinitializeThisFirst();
    DeinitializeThisAfterwards();
}

And since I am on the UI thread, this will cause a deadlock:

public void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    DeinitializeThisFirst().ConfigureAwait(false).GetAwaiter().GetResult();
    DeinitializeThisAfterwards();
}

Executing it in a separate Task will cause DeinitializeThisAfterwards() to be executed prematurely:

public void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    Task.Run(async () => await DeinitializeThisFirst());
    DeinitializeThisAfterwards();
}

And wrapping it in ContinueWith() will prevent both from being executed since the window is closed immediately:

public void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    DeinitializeThisFirst().ContinueWith(_ =>
    {
        DeinitializeThisAfterwards();
    });
}

I am at a loss here. Can anyone help?


Solution

  • You have to Cancel the initial Closing of the window, and Close it programmatically later when the deinitialization has completed. The following example should give you an idea what to do:

    private bool _deinitializeRunning = false;
    private bool _deinitializeFinished = false;
    
    public async void Window_Closing(object sender, CancelEventArgs e)
    {
        if (_deinitializeFinished) return; // Allow the window to close
    
        e.Cancel = true;
        if (_deinitializeRunning) return; // Cancel the close and return
    
        _deinitializeRunning = true;
        try
        {
            await DeinitializeThisFirst();
            DeinitializeThisAfterwards();
        }
        finally { _deinitializeRunning = false; }
        _deinitializeFinished = true;
       
        await Task.Yield(); // Ensure that the Close is called asynchronously
        Close();
    }
    

    You have to anticipate for three states of the deinitialization process:

    1. The deinitialization has not started yet.
    2. The deinitialization is in progress but not completed yet.
    3. The deinitialization has completed.

    Each state requires different actions inside the Closing event handler.