长时任务触发更新事件引发的竞态条件问题排查与解决
问题根源分析
你的代码出现竞态条件并触发空引用异常,核心原因有两点:
- 变量拼写错误:
OnUpdateEvent方法中误用了_curentlyRunningItem(少了一个'r'),实际类中定义的字段是_currentlyRunningItem,直接导致访问未绑定的变量; ConfigureAwait(false)的误用:该配置让await后的代码(finally块)在非UI线程执行,即便Dispatcher.Invoke是同步调用,多线程调度的不确定性仍可能导致finally块先于UI线程的事件处理完成,提前将字段设为null。
此外,Task.Factory.StartNew的默认调度器可能引发意外线程行为,建议改用Task.Run明确将长时任务放到线程池执行。
解决方案
方案一:修复拼写错误并移除ConfigureAwait(false)
这是最直接的修复方式,确保await后的代码回到UI线程执行,保证事件处理完成后再清空字段:
public class DemoViewModel { private DemoRunClass DemoRunner; private ItemViewModel _currentlyRunningItem; public async Task RunNextItem() { var nextRunnableItemViewModel = GetNextItem(); var runItemCommand = GenerateCommand(nextRunnableItemViewModel); _currentlyRunningItem = nextRunnableItemViewModel; try { // 移除ConfigureAwait(false),确保await后回到UI线程 await DemoRunner.ExecuteLongRunningTask(runItemCommand, Dispatcher.CurrentDispatcher); } catch (Exception ex) { // 异常处理逻辑 } finally { _currentlyRunningItem = null; } } private void OnUpdateEvent(object status) { if (status == "Method1 complete") _currentlyRunningItem.OnMethod1Complete(); // 修正拼写错误 // ... else if (status == "All Complete") _currentlyRunningItem.OnAllComplete(); // 修正拼写错误 } }
方案二:改用Task.Run执行长时任务
替换Task.Factory.StartNew为Task.Run,明确将任务放到线程池执行,避免UI线程阻塞或意外调度:
public class DemoRunClass { public event EventHandler<object> ExecutionUpdate; public Dispatcher EventDispatcher; public Task ExecuteLongRunningTask(object command, Dispatcher dispatcher) { EventDispatcher = dispatcher; // 改用Task.Run,确保任务在ThreadPool线程执行 return Task.Run(() => { Method1(); Method2(); Method3(); Method4(); Method5(); UpdateEvent("All Complete"); }); } // 其余代码保持不变 }
方案三:传递Item到事件中(更健壮的设计)
彻底消除对共享字段的依赖,将ItemViewModel直接作为事件参数传递,从根源上避免竞态:
- 修改
DemoRunClass的事件定义:
// 自定义事件参数 public class ExecutionUpdateEventArgs : EventArgs { public object Status { get; } public ItemViewModel Item { get; } public ExecutionUpdateEventArgs(object status, ItemViewModel item) { Status = status; Item = item; } } public class DemoRunClass { public event EventHandler<ExecutionUpdateEventArgs> ExecutionUpdate; public Dispatcher EventDispatcher; // 添加ItemViewModel参数 public Task ExecuteLongRunningTask(object command, Dispatcher dispatcher, ItemViewModel item) { EventDispatcher = dispatcher; return Task.Run(() => { Method1(item); Method2(item); Method3(item); Method4(item); Method5(item); UpdateEvent("All Complete", item); }); } public void UpdateEvent(object status, ItemViewModel item) { if (!EventDispatcher.CheckAccess()) EventDispatcher.Invoke(new Action<object, ItemViewModel>(UpdateEvent), status, item); else { ExecutionUpdate?.Invoke(null, new ExecutionUpdateEventArgs(status, item)); } } private void Method1(ItemViewModel item) { // 执行操作 UpdateEvent("Method1 complete", item); } // Method2-Method5同理,接收ItemViewModel参数 }
- 修改
DemoViewModel的调用和事件处理:
public class DemoViewModel { private DemoRunClass DemoRunner; public async Task RunNextItem() { var nextRunnableItemViewModel = GetNextItem(); var runItemCommand = GenerateCommand(nextRunnableItemViewModel); try { // 传递ItemViewModel给任务方法 await DemoRunner.ExecuteLongRunningTask(runItemCommand, Dispatcher.CurrentDispatcher, nextRunnableItemViewModel); } catch (Exception ex) { // 异常处理 } // 不再需要共享字段和finally块 } private void OnUpdateEvent(object sender, ExecutionUpdateEventArgs e) { if (e.Status == "Method1 complete") e.Item.OnMethod1Complete(); else if (e.Status == "All Complete") e.Item.OnAllComplete(); } }
内容的提问来源于stack exchange,提问作者user2790954
相关产品推荐
相关产品推荐

