WPF中Task.Run导致数据重复入库如何处理及如何终止Task.Run任务
问题1:数据库重复插入数据原因
- 首先你的Task用法存在根本错误:所有数据库插入逻辑都被包裹在
Dispatcher.Invoke中,相当于后台线程启动后立刻切回UI线程执行全部耗时操作,Task.Run完全没有起到后台异步执行的作用,UI仍会卡顿。 - 重复插入的核心原因是未等待异步任务执行完成就执行后续逻辑,产生了竞态条件:
你调用Task.Run后没有加await等待保存完成,UI线程会立刻执行后续的GridView刷新逻辑,将旧数据重新绑定到GridView。而后台线程拿到的Gridview.Items是动态集合,遍历的时候会同时取到绑定前后的两份数据,合计600条全部插入。 - 额外逻辑缺陷:
MessageBox.Show("Save Done")在保存操作还未启动时就弹出,和实际执行进度完全不符,同时未做按钮点击防抖,快速点击按钮会触发多次插入任务。
问题2:终止Task.Run启动的任务
使用CancellationTokenSource实现任务的主动终止,步骤如下:
- 在窗口类中声明
CancellationTokenSource类型的全局字段,用于存储取消令牌源 - 每次启动保存任务前初始化令牌源,将令牌传入Task.Run的委托中
- 在循环插入的每一步检查令牌的取消状态,收到取消信号后立刻终止循环
- 需要终止任务时,调用
CancellationTokenSource.Cancel()方法即可
修复后完整代码
// 窗口类全局字段 private CancellationTokenSource _saveCts; private async void SaveItem(object sender, RoutedEventArgs e) { // 取消上一次未完成的保存任务 _saveCts?.Cancel(); _saveCts = new CancellationTokenSource(); var cancelToken = _saveCts.Token; // 提前复制要保存的数据,避免动态集合变化导致重复插入 var itemsToSave = Gridview.Items.Cast<ItemsModel>().ToList(); if (itemsToSave.Count == 0) return; var saveBtn = sender as Button; saveBtn.IsEnabled = false; PleaseWaittxt.Visibility = Visibility.Visible; try { bool saveSuccess = await Task.Run(() => { var controller = new Controller(); foreach (var item in itemsToSave) { // 检测是否收到取消信号 if (cancelToken.IsCancellationRequested) return false; bool addResult = controller.AddItem(item); if (!addResult) { Dispatcher.Invoke(() => MessageBox.Show("Add failed")); return false; } Console.WriteLine("Add true to Items"); } return true; }, cancelToken); if (saveSuccess && !cancelToken.IsCancellationRequested) { MessageBox.Show("Save Done"); // 保存完成后再刷新列表 Gridview.ItemsSource = new Controller().GetAllItems(); } } catch (OperationCanceledException) { MessageBox.Show("Save operation cancelled"); } finally { // 恢复UI状态 PleaseWaittxt.Visibility = Visibility.Hidden; saveBtn.IsEnabled = true; _saveCts.Dispose(); _saveCts = null; } }
如果需要主动终止任务,只需要在取消按钮的点击事件等位置调用_saveCts?.Cancel()即可。
内容的提问来源于stack exchange,提问作者csharp_devloper31
相关产品推荐
相关产品推荐

