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

C# Parallel.ForEach并行处理文件时偶发卡死问题排查

Parallel.ForEach偶发卡死问题排查

你的代码存在多个会导致卡死、执行异常的编写问题,核心问题如下:

  • 线程安全问题(卡死核心原因):代码中使用的areaDataCRCs、filesToMove、gr2Files均为非线程安全的普通集合(大概率是List<T>)。Parallel.ForEach会启动多个线程同时执行循环逻辑,多线程同时对普通集合执行Contains、Add、Remove操作时,会破坏集合内部的索引、数组结构,轻则出现数据丢失、异常,重则触发集合内部遍历的无限循环,表现为程序偶发卡死。
  • 文件读取逻辑Bug:外层while ((line = sr.ReadLine()) != null)已经读取了一行内容,进入循环后立刻又执行了一次line = sr.ReadLine(),会直接跳过所有奇数行内容;如果文件行数为奇数,最后一次读取会得到null,直接传入areaDataCRCs.Contains(null)会触发空引用异常,并行场景下未捕获的异常可能导致任务异常终止、状态错乱。
  • 并行度设置无效:开头写的Math.Max(2, Environment.ProcessorCount - 2);没有赋值给任何变量,完全不生效。另外读文件属于IO密集型操作,不是CPU密集型计算,默认的并行度会启动过多线程抢占磁盘IO资源,反而会降低处理速度。
  • 后缀判断逻辑不严谨:使用allPrFile.IndexOf(".prb") > -1判断文件类型,会把文件名包含.prb但后缀不是prb的文件(比如xxx.prb_bak)误判为目标文件。
修正后参考代码
// 设置合理的IO并行度,不要超过磁盘并发能力,一般4-8即可
int parallelCount = Math.Min(8, Math.Max(2, Environment.ProcessorCount - 2));
// 用线程安全集合替换普通List,ConcurrentDictionary做去重存储性能远高于ConcurrentBag
ConcurrentDictionary<string, byte> areaDataCRCs = new ConcurrentDictionary<string, byte>(/* 初始化把你的CRC项传进去,值传0即可 */);
ConcurrentDictionary<string, byte> filesToMove = new ConcurrentDictionary<string, byte>();
ConcurrentDictionary<string, byte> gr2Files = new ConcurrentDictionary<string, byte>();

Parallel.ForEach(allPrFiles, new ParallelOptions() { MaxDegreeOfParallelism = parallelCount }, allPrFile =>
{
    // 先判断文件后缀,避免打开不需要处理的文件浪费性能
    if (Path.GetExtension(allPrFile)?.ToLower() != ".prb")
    {
        // 非prb文件只需要逐行判断是否包含CRC项即可
        using (StreamReader sr = new StreamReader(allPrFile))
        {
            string? line;
            while ((line = sr.ReadLine()) != null)
            {
                if (!string.IsNullOrEmpty(line) && areaDataCRCs.ContainsKey(line))
                {
                    Dbg.DebugTxt(allPrFile);
                    areaDataCRCs.TryRemove(line, out _);
                    break;
                }
            }
        }
        return;
    }

    // 处理prb文件
    using (StreamReader sr = new StreamReader(allPrFile))
    {
        string? line;
        bool foundCrc = false;
        while ((line = sr.ReadLine()) != null)
        {
            if (string.IsNullOrEmpty(line)) continue;
            if (areaDataCRCs.ContainsKey(line))
            {
                Dbg.DebugTxt(allPrFile);
                areaDataCRCs.TryRemove(line, out _);
                foundCrc = true;
                filesToMove.TryAdd(allPrFile, 0);
                break;
            }
        }

        if (!foundCrc) return;

        // 继续读后续行找gr2文件
        while ((line = sr.ReadLine()) != null)
        {
            if (string.IsNullOrEmpty(line)) continue;
            line = line.Replace("buildingfile", string.Empty);
            line = line.Replace("dungeonblockfile", string.Empty);
            line = line.Replace("\"", string.Empty);
            line = line.Trim();
            if (string.IsNullOrEmpty(line))
            {
                Dbg.DebugTxt("NO GR2 FILE FOUND - " + allPrFile);
                break;
            }
            gr2Files.TryAdd(line, 0);
            filesToMove.TryAdd(line, 0);
#if DEBUG
            Dbg.DebugTxt(line);
#endif
            break;
        }
    }
});

// 如果最终需要List结果,执行完并行后再从ConcurrentDictionary转换即可
List<string> finalFilesToMove = filesToMove.Keys.ToList();
List<string> finalGr2Files = gr2Files.Keys.ToList();

注:代码中字符串裁剪部分按照你的说明保留了原有测试逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:48:26