You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:13:26