Home > database >  How to access UI component from Task in Window Closing event correctly?
How to access UI component from Task in Window Closing event correctly?

Time:10-05

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?

CodePudding user response:

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