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

C# Task与BackgroundWorker线程安全、任务取消及异常问题咨询

问题解答

1. for循环迭代变量超出集合长度问题

这个是C#经典的循环变量闭包捕获问题:for循环中定义的迭代变量i是整个循环周期内唯一的变量,你在创建Task的lambda表达式中直接捕获了i,而不是捕获循环执行到当前步骤时i的值。当Task真正开始执行时,for循环往往已经执行完毕,此时i的值已经变成了循环终止值(比如_cars.Count为4时,i最终会自增到4,超出索引上限3),就会出现索引越界的情况,调试时观测到i为5大概率是循环过程中_cars长度动态变化叠加捕获问题导致的。
修复方案:在for循环内部定义局部变量接收当前i的值,lambda捕获该局部变量即可:

for (int i = 0; i < _cars.Count; i++)
{
    // 新增局部变量缓存当前索引
    int currentIndex = i;
    if (_cars[currentIndex].Task != null && _cars[currentIndex].Task.Status != TaskStatus.Running && _cars[currentIndex].Task.Status != TaskStatus.Created)
    {
        Console.WriteLine($"Making new task for { _cars[currentIndex].Model }");
        _cars[currentIndex].tokenSource.Cancel();
        _cars[currentIndex].RefreshToken();
        _cars[currentIndex].Task = null;
    }
    if (_cars[currentIndex].Task == null)
    {
        // 用缓存的currentIndex代替原迭代变量i
        _cars[currentIndex].Task = new Task(() => new Manufacture.Build().MakeCar(_cars[currentIndex].Model), _cars[currentIndex].cancellationToken);
        _cars[currentIndex].Task.Start();
    }
}

如果你使用的是C# 5.0以下版本,foreach的循环变量也存在同样的闭包捕获问题,同样需要在循环内定义局部变量接收car的值再传入lambda。

2. 多个任务同时报错问题

这个问题确实和Random类的特性直接相关:Random的默认构造函数使用系统时钟作为种子,而系统时钟的精度只有10~15ms。你所有Task几乎同时启动,每个Task的MakeCar方法每次循环都重新new Random(),这会导致多个Random实例拿到相同的系统时钟种子,生成的随机数序列完全一致,自然会同时命中chaos>=90的异常触发条件,即使你把概率调低到1%也会同时触发。
修复方案:将Random改为静态实例,加锁访问,避免重复创建:

namespace ThreadTest.Manufacture
{
    class Build
    {
        // 全局唯一的Random实例
        private static readonly Random _random = new Random();
        private static readonly object _lockObj = new object();
        public void MakeCar(string car)
        {
            try
            {
                while (true)
                {
                    int chaos;
                    // 加锁保证随机数获取线程安全
                    lock(_lockObj)
                    {
                        chaos = _random.Next(0, 100);
                    }
                    Console.WriteLine($"Building {car}");
                    Thread.Sleep(2000);
                    if (chaos >= 90) throw new Exception($"Something went wrong with {car}");
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
                throw;
            }
        }
    }
}

3. 任务取消逻辑的问题

你的取消逻辑不符合规范,核心问题是取消令牌没有生效:你给Task传入了cancellationToken,但在MakeCar的执行逻辑中完全没有检查该令牌的取消状态,就算你调用了tokenSource.Cancel(),任务也不会响应取消,会一直执行到抛出异常或者正常结束。
修复方案:在MakeCar的循环中加入取消检查:

// 方法参数传入取消令牌
public void MakeCar(string car, CancellationToken token)
{
    try
    {
        // 每次循环都检查是否触发取消
        while (!token.IsCancellationRequested)
        {
            // 原有业务逻辑
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
        throw;
    }
}

另外你现有实现还有以下可优化点:

  • Timer的Elapsed事件中用空循环等待BackgroundWorker空闲会无意义占用CPU资源,建议将Timer的AutoReset属性设为false,每次BackgroundWorker执行完成后再手动调用Timer的Start方法。
  • Task抛出异常后你没有处理异常观察,未被观察的Task异常会在GC回收时触发UnobservedTaskException,严重时会导致程序崩溃,建议要么在Task的延续操作中处理异常,要么注册全局的UnobservedTaskException事件处理逻辑。
  • 直接判断Task状态的方式不够可靠,建议结合取消令牌和任务延续逻辑来管理任务的生命周期,而不是依赖状态判断。

内容的提问来源于stack exchange,提问作者user1257758

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 15:48:03