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