WPF应用中CancellationToken字典状态异常问题求助
摄像头捕获任务跟踪Dictionary状态丢失问题排查与解决
问题背景
使用C#开发对接本地及IP摄像头的WPF应用,基于AForge.Video.DirectShow实现视频捕获,通过CancellationTokenSource管理任务取消逻辑,并用Dictionary<int, CancellationTokenSource>以摄像头索引为键跟踪各摄像头的任务令牌。当前遇到的问题是:创建新任务时存入的CancellationTokenSource,后续用同一摄像头索引检索时无法找到对应条目,跨LocalCamera_ComboBox_SelectionChanged方法调用时Dictionary状态无法维持。
相关代码
private async void LocalCamera_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { System.Windows.Controls.ComboBox comboBoxCamera = sender as System.Windows.Controls.ComboBox; // 修正原代码的控件引用错误:使用当前触发事件的comboBoxCamera而非SourceComboBox int cameraIndex = int.Parse(comboBoxCamera.Name.Replace("comboBox", "")); int selectedItemIndex = comboBoxCamera.SelectedIndex; Image imageControl = comboBoxCamera.Tag as Image; Debug.WriteLine($"selectedItemIndex: {selectedItemIndex}"); Debug.WriteLine($"cameraIndex: {cameraIndex}"); Debug.WriteLine($"tokenSources contains cameraIndex: {tokenSources.ContainsKey(cameraIndex)}"); // 选中第一项时取消当前任务 if (selectedItemIndex == 0) { if (tokenSources.ContainsKey(cameraIndex)) { var cts = tokenSources[cameraIndex]; cts.Cancel(); cts.Dispose(); tokenSources.Remove(cameraIndex); Debug.WriteLine($"CancellationTokenSource for cameraIndex {cameraIndex} cancelled and removed."); } } else { // 选中其他项时启动新捕获任务 var newCts = new CancellationTokenSource(); tokenSources[cameraIndex] = newCts; Debug.WriteLine($"New CancellationTokenSource created and stored for cameraIndex {cameraIndex}."); // 修正原代码的控件引用错误:使用imageControl而非SourceImageControl await Task.Factory.StartNew(() => LocalCameraService.Instance.CaptureCameraCallback(selectedItemIndex - 1, imageControl, newCts.Token), newCts.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Debug.WriteLine($"tokenSources count after operations: {tokenSources.Count}"); }
异常现象
存入新的CancellationTokenSource后,tokenSources.Count正常变为1,但后续触发事件检查tokenSources.ContainsKey(cameraIndex)时返回false;已确认cameraIndex未发生意外变动,且未找到代码中存在重新初始化tokenSources的逻辑。
排查方向与解决方案
- 确认Dictionary的作用域:确保
tokenSources是类级别的成员变量,而非方法内的局部变量。如果是局部变量,每次触发事件都会创建新的空字典,自然无法维持状态。正确声明方式:private Dictionary<int, CancellationTokenSource> tokenSources = new Dictionary<int, CancellationTokenSource>(); - 解决线程安全问题:
Dictionary<TKey,TValue>不是线程安全集合,UI线程和异步任务线程并发读写会导致状态异常。改用线程安全的ConcurrentDictionary<int, CancellationTokenSource>:
同步替换对应的操作逻辑:private ConcurrentDictionary<int, CancellationTokenSource> tokenSources = new ConcurrentDictionary<int, CancellationTokenSource>();// 存入令牌(线程安全) tokenSources.TryAdd(cameraIndex, newCts); // 移除并处理令牌(线程安全) if (tokenSources.TryRemove(cameraIndex, out var cts)) { cts.Cancel(); cts.Dispose(); } - 检查UI组件生命周期:如果ComboBox所在的UI元素(如UserControl、Page)被重复实例化,类成员变量
tokenSources会被重置为空。确认页面导航、控件加载逻辑是否存在重复创建控件实例的情况。 - 修正代码中的变量混淆:原代码中错误使用了
SourceComboBox和SourceImageControl,应替换为当前触发事件的comboBoxCamera和imageControl,避免因控件引用错误导致cameraIndex计算异常。
内容的提问来源于stack exchange,提问作者Captain C
相关产品推荐
相关产品推荐

