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

C#中Parallel.ForEach抛索引越界异常但foreach正常的问题

问题描述

我写的程序需要检查数组所有元素的状态,用foreach运行正常,但换成Parallel.ForEach或Parallel.For就会抛出索引越界异常。我没有修改集合_currentSpherePoints,里面是自定义的Vector类,不知道问题出在哪。

正常运行的foreach代码:

foreach (var p in _currentSpherePoints)
{
    point = p + center;
    point.X = (point.X + _field.GetLength((int)VectorInt.Dimension.X)) % _field.GetLength((int)VectorInt.Dimension.X);
    point.Y = (point.Y + _field.GetLength((int)VectorInt.Dimension.Y)) % _field.GetLength((int)VectorInt.Dimension.Y);
    point.Z = (point.Z + _field.GetLength((int)VectorInt.Dimension.Z)) % _field.GetLength((int)VectorInt.Dimension.Z);
    if (_field[point.X, point.Y, point.Z] != 0)
    {
        isCrossed = true;
        break;
    }
}

抛出异常的Parallel.ForEach代码:

public bool IsSphereCrossed(VectorInt center)
{
    bool isCrossed = false;
    var point = new VectorInt(0, 0, 0);

    Parallel.ForEach(_currentSpherePoints, (p, state) =>
    {
        point = p + center;
        point.X = (point.X + _field.GetLength((int)VectorInt.Dimension.X)) % _field.GetLength((int)VectorInt.Dimension.X);
        point.Y = (point.Y + _field.GetLength((int)VectorInt.Dimension.Y)) % _field.GetLength((int)VectorInt.Dimension.Y);
        point.Z = (point.Z + _field.GetLength((int)VectorInt.Dimension.Z)) % _field.GetLength((int)VectorInt.Dimension.Z);
        if (_field[point.X, point.Y, point.Z] != 0)
        {
            isCrossed = true;
            state.Stop();
        }
    });
    return isCrossed;
}
问题原因
  • 共享变量竞争:point是在Parallel.ForEach外部声明的共享变量,多个并行线程会同时读写这个变量。比如线程A刚计算完point.X,线程B就把point重新赋值为另一个向量,导致线程A后续计算point.Y、point.Z时用的是线程B的向量值,最终生成的坐标完全混乱,超出_field的索引范围,直接触发越界异常。
  • isCrossed的线程安全隐患:多个线程同时修改这个布尔变量,虽然布尔赋值是原子操作,但结合state.Stop()的逻辑,可能出现线程设置isCrossed后,其他线程还在继续执行的情况,不过这不是索引越界的直接原因。
修复方案

方案1:使用局部变量避免共享竞争

把point移到Parallel.ForEach的委托内部,让每个线程都使用自己独立的局部变量,同时用Interlocked保证isCrossed的线程安全赋值:

public bool IsSphereCrossed(VectorInt center)
{
    bool isCrossed = false;

    Parallel.ForEach(_currentSpherePoints, (p, state) =>
    {
        // 每个线程单独创建point变量,避免跨线程竞争
        var point = p + center;
        int xLength = _field.GetLength((int)VectorInt.Dimension.X);
        point.X = (point.X + xLength) % xLength;
        int yLength = _field.GetLength((int)VectorInt.Dimension.Y);
        point.Y = (point.Y + yLength) % yLength;
        int zLength = _field.GetLength((int)VectorInt.Dimension.Z);
        point.Z = (point.Z + zLength) % zLength;
        
        if (_field[point.X, point.Y, point.Z] != 0)
        {
            // 原子操作赋值,确保线程安全
            Interlocked.Exchange(ref isCrossed, true);
            state.Stop();
        }
    });
    return isCrossed;
}

方案2:提前缓存维度长度优化性能

可以提前缓存_field的各维度长度,避免在并行循环中重复调用GetLength,进一步提升效率:

public bool IsSphereCrossed(VectorInt center)
{
    bool isCrossed = false;
    // 提前缓存维度长度,减少重复计算
    int xLength = _field.GetLength((int)VectorInt.Dimension.X);
    int yLength = _field.GetLength((int)VectorInt.Dimension.Y);
    int zLength = _field.GetLength((int)VectorInt.Dimension.Z);

    Parallel.ForEach(_currentSpherePoints, (p, state) =>
    {
        var point = p + center;
        point.X = (point.X + xLength) % xLength;
        point.Y = (point.Y + yLength) % yLength;
        point.Z = (point.Z + zLength) % zLength;
        
        if (_field[point.X, point.Y, point.Z] != 0)
        {
            Interlocked.Exchange(ref isCrossed, true);
            state.Stop();
        }
    });
    return isCrossed;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 09:43:28