admin管理员组文章数量:1312978
I need to perform an async operation when the window is closed (basically, to save all my data). The problem is that the program and the UI thread dies too soon and the task never completes.
This is the code example:
public partial class MainWindow : Window
{
public MainWindow()
{
Closed += MainWindow_Closed;
_items = new(Enumerable.Range(0,10));
}
private ObservableCollection<int> _items;
private async void MainWindow_Closed(object? sender, EventArgs e)
{
await LongTask();
}
private async Task LongTask()
{
await Task.Run(() =>
{
Trace.WriteLine("LongTask Start");
Application.Current.Dispatcher.BeginInvoke(() =>
{
// do some action in the UI thread
_items.Clear();
Trace.WriteLine("Cleared elements");
});
Thread.Sleep(5000);
Trace.WriteLine("LongTask End");
});
}
}
and the console output never prints 'Cleared elements' nor 'LongTask End'. I have put also an action inside the async task that needs the UI thread to complete the operation, which is needed in my use case. (Thus one cannot use LongTask().GetAwaiter().GetResult() in the main thread as it would result in a deadlock).
Thanks for the help!!
I need to perform an async operation when the window is closed (basically, to save all my data). The problem is that the program and the UI thread dies too soon and the task never completes.
This is the code example:
public partial class MainWindow : Window
{
public MainWindow()
{
Closed += MainWindow_Closed;
_items = new(Enumerable.Range(0,10));
}
private ObservableCollection<int> _items;
private async void MainWindow_Closed(object? sender, EventArgs e)
{
await LongTask();
}
private async Task LongTask()
{
await Task.Run(() =>
{
Trace.WriteLine("LongTask Start");
Application.Current.Dispatcher.BeginInvoke(() =>
{
// do some action in the UI thread
_items.Clear();
Trace.WriteLine("Cleared elements");
});
Thread.Sleep(5000);
Trace.WriteLine("LongTask End");
});
}
}
and the console output never prints 'Cleared elements' nor 'LongTask End'. I have put also an action inside the async task that needs the UI thread to complete the operation, which is needed in my use case. (Thus one cannot use LongTask().GetAwaiter().GetResult() in the main thread as it would result in a deadlock).
Thanks for the help!!
Share Improve this question edited Feb 1 at 17:31 Ivan Petrov 5,1202 gold badges11 silver badges23 bronze badges asked Feb 1 at 17:22 Juan Manzanero TorricoJuan Manzanero Torrico 434 bronze badges 12 | Show 7 more comments2 Answers
Reset to default 3As a proof of concept, here's one way you could implement the exceedingly common and routine goal of asynchronously saving your data upon app close. The key here is the _confirmClosure
bool, because if the user wants to "save and close" then we're going to:
- Disable the UI to prevent any chance of reentry, but cancel the
Close()
. - Asynchronously execute the save.
- Then, after awaiting the save, call
Close()
again, this time with_confirmClosure
set tofalse
.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += async (sender, e) =>
{
if (_confirmClosure)
{
switch (
MessageBox.Show(
"Do you want to save before closing?",
"Confirm Exit",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question))
{
case MessageBoxResult.Yes:
e.Cancel = true;
await Dispatcher.BeginInvoke(async () =>
{
try
{
Mouse.OverrideCursor = Cursors.Wait;
IsEnabled = false; // Prevent any more calls.
await DataContext.Save();
_confirmClosure = false;
Close();
}
finally
{
Mouse.OverrideCursor = null;
}
});
break;
case MessageBoxResult.No:
break;
case MessageBoxResult.Cancel:
e.Cancel = true;
break;
}
}
};
}
bool _confirmClosure = true;
new MainPageViewModel DataContext => (MainPageViewModel)base.DataContext;
}
Minimal VM for Test
We'll make a "big" list of 10000 items, then save it to a file upon close.
public class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>(
Enumerable.Range(1, 10000)
.Select(_=>new Item { Id = _, Name = $"Item {_}" }));
public event PropertyChangedEventHandler PropertyChanged;
internal async Task Save()
{
await Task.Run(() =>
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"StackOverflow",
Assembly.GetEntryAssembly()?.GetName()?.Name ?? "SaveThenClose",
"list-data.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
var json = JsonConvert.SerializeObject(Items, Formatting.Indented);
File.WriteAllText(path, json);
});
// Add a few seconds for good measure, just for demo purposes.
await Task.Delay(TimeSpan.FromSeconds(2.5));
}
}
public class Item
{
public int Id { get; set; }
public string? Name { get; set; }
}
<Window x:Class="save_list_then_close.MainWindow"
xmlns="http://schemas.microsoft/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats./markup-compatibility/2006"
xmlns:local="clr-namespace:save_list_then_close"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="400"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainPageViewModel/>
</Window.DataContext>
<Grid>
<DataGrid
Name="dataGrid"
ItemsSource="{Binding Items}"
AutoGenerateColumns="True"
IsReadOnly="True"
AlternatingRowBackground="LightGray"/>
</Grid>
</Window>
@IV answer is correct. Just adding some additional context. The key here is a difference in Closing versus Closed.
event CancelEventHandler Closing;
event EventHandler Closed;
In Closed, the process is terminating and any process you start here is doomed to be terminated with the main thread. In Closing, you have the option to intercept the operation and perform additional tasks if necessary.
本文标签: cWPF Asynchronous operation on window closeStack Overflow
版权声明:本文标题:c# - WPF: Asynchronous operation on window close - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741869205a2402092.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
[Window].Closing
event, so you can cancel the closing procedure, if that becomes necessary, or by user choice. Not clear, though, why you would do some action related to the UI itself, as clearing a collection of items, unless it's just an example. Anyway, if you await a procedure in theClosed
event, the flow moves out the event handler in the meanwhile, so the window closes – Jimi Commented Feb 1 at 18:00