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

C# Parallel.ForEach循环漏执行、重复执行问题排查

并行代码迭代遗漏、重复执行问题排查

我有两套可实现相同功能的代码逻辑,一套为串行执行版本,另一套为并行执行版本。由于总迭代规模约为10万次,量级较大,因此采用并行化方案优化执行效率。但并行版本运行时出现异常:部分迭代被跳过未执行,部分迭代被重复执行,暂未找到有效的调试排查思路。
两套实现除了将普通foreach循环替换为Parallel.ForEach循环外,其余逻辑完全一致。

串行方法实现代码

for(int i = somenum; i >= 0; i-- ){

    foreach (var nue in nuelist)
    {
        foreach (var path in nue.pathlist)
        {
            foreach (var conn in nue.connlist)
            {
                Func(conn,path); 
            }
        }
    }
}

并行方法实现代码

for(int i = somenum; i >= 0; i-- ){

    Parallel.ForEach(nuelist,nue =>
    {
        Parallel.ForEach(nue.pathlist,path=>
        {
            Parallel.ForEach(nue.connlist, conn=>
            {
                Func(conn,path);
            });
        });
    });
}

Path类内部实现代码

Nue firstnue;
public void Func(Conn conn,Path path)
{
    List<Conn> list = new(){conn};
    list.AddRange(path.list);
    _ = new Path(list); 
}
public Path(List<Conn>)
{
   //other things
   firstnue.pathlist.Add(this);
   /*
   firstnue 属于外层for循环下一轮迭代会处理的nue对象
   */
}

问题根因

并行版本执行结果不符合预期的核心原因是线程安全冲突,具体分为两点:

  • 遍历非线程安全集合时并发修改集合:Path构造函数中会向firstnue.pathlist新增元素,firstnue属于后续外层i循环的遍历对象。串行执行时新增的元素只会在后续轮次被遍历,不会干扰当前轮次逻辑;但并行执行时多线程同时对普通List<T>做读写操作——一边遍历pathlist,一边往里面执行Add操作,会直接导致List<T>内部索引结构错乱,既可能让新增元素提前被当前轮次遍历到(造成重复执行),也可能让原有元素被跳过,甚至触发“集合已修改,无法执行枚举操作”的异常。三层嵌套Parallel.ForEach会过度占用线程池资源,进一步放大这类冲突的出现概率。
  • 嵌套并行打乱原有执行时序:Parallel.ForEach是阻塞式执行,多层嵌套会让线程调度逻辑失控,无法保证串行版本中“当前外层i轮次所有逻辑执行完成后,再进入下一轮i循环”的执行顺序,进一步加剧执行结果和预期不符的问题。

修复方案

  • 每轮外层i循环启动时,先对当前需要遍历的所有集合做只读快照,遍历快照而非原始集合,从根源上避免遍历过程中集合变动的干扰:
for(int i = somenum; i >= 0; i-- ){
    // 生成当前轮次待处理数据的快照,遍历过程不直接操作原集合
    var currentNues = nuelist.ToArray();
    Parallel.ForEach(currentNues, nue =>
    {
        var currentPaths = nue.pathlist.ToArray();
        var currentConns = nue.connlist.ToArray();
        // 内层用普通foreach即可,不需要嵌套并行
        foreach (var path in currentPaths)
        {
            foreach (var conn in currentConns)
            {
                Func(conn, path);
            }
        }
    });
}
  • 移除不必要的嵌套Parallel.ForEach,仅保留最外层一层并行即可。内层循环用普通foreach执行,既减少线程调度的额外开销,也能保证执行时序和串行版本一致。
  • 如果存在多线程同时写入同一个集合的场景,要么对写入逻辑加锁保护,要么替换为ConcurrentBag<T>这类线程安全集合,避免并发写冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 08:31:09