0

I have a WPF application. In the window closing event i need to call an async method with return value that itself needs to access UI components.

To run this code, create a fresh wpf application, add this line to the MainWindow.xaml

Closing="Window_Closing"

and this code to the MainWindow.xaml.cs

private async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    Console.WriteLine("Start");

    // while the method is awaited, Window_Closing gives the control back to the caller
    // since Window_Closing is void, WPF goes on and closes the application
    // before the long running task has been finished => FAIL
    var result = await Test();

    // creates a dead lock. Breakpoint on the Task.Delay(1000) is hit
    // then nothing happens and the application is locked => FAIL
    var result = Test().Result;

    // Task.Delay(1000) works, but when the Background shall be accessed...
    // "The calling thread cannot access this object because a different thread owns it."
    // => Fail
    var result = Task.Run(() => Test()).Result;

    // same like the first try. 
    // WPF goes on and closes the application
    // before the result has ever been set => FAIL
    var result = false;
    await Application.Current.Dispatcher.BeginInvoke(async () =>
    {
        result = await Test();
    });

    Console.WriteLine(result);
    e.Cancel = result;
}

public async Task<bool> Test()
{
    await Task.Delay(1000);

    this.Background = Brushes.AliceBlue;

    return true;
}

How (without changing the Test method) do i get the desired/expected behavior?

Martin Booka Weser
  • 3,192
  • 5
  • 28
  • 41
  • Related: [Awaiting Asynchronous function inside FormClosing Event](https://stackoverflow.com/questions/16656523/awaiting-asynchronous-function-inside-formclosing-event) – Theodor Zoulias Sep 29 '22 at 12:51
  • Could you include in the question the expected/desirable behavior as normal text, apart from including it in the comments inside the code? – Theodor Zoulias Sep 29 '22 at 12:55

1 Answers1

2

Set e.Cancel = true; before any async call. If the Window shall be closed later, call Close() after detaching the Closing event handler.

A simple example:

private async void Window_Closing(object sender, CancelEventArgs e)
{
    e.Cancel = true;

    if (!await Test()) // your Test method or the one below
    {
        Closing -= Window_Closing;
        Close();
    }
}

Use a Dispatcher invocation to access UI elements from a Task action that runs in a background thread. For reference, see The calling thread cannot access this object because a different thread owns it.

public Task<bool> Test()
{
    // a long running task on a background thread

    return Task.Run(() =>
    {
        Dispatcher.Invoke(() =>
        {
            Background = Brushes.AliceBlue;
        });

        Thread.Sleep(1000);

        return new Random().Next(2) == 0; // close or not
    });
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • to be precise: I asked "without changing the Test() method". This is currently not given in your answer. However, the approach works for me. I call the entire "Test()" method in Dispatcher.Invoke right away. While this is not optimal (i run the potentially long running thing on the dispatcher) it works for me! `e.Cancel = true; var result = false; await Dispatcher.Invoke(async () => { result = await Test(); }); if (result) { Closing -= Window_Closing; Close(); }` – Martin Booka Weser Oct 04 '22 at 07:34
  • See the comment: `// your Test method or the one below` Be aware that what you are showing in your comment is not "*run[ning] the potentially long running thing on the dispatcher*". You could drop the Dispatcher call completely. – Clemens Oct 04 '22 at 07:35