OutOfRangeException with Parallel.For

本文关键字:For Parallel with OutOfRangeException | 更新日期: 2023-09-27 17:55:29

好的,所以我有一个工作正常的程序。在它里面有一个可以并行化的 for 循环。所以我用Parallel.For来这样做。它工作正常一两次,但其他时候有以下例外:

未指定的错误 发生一个或多个错误

没有进一步的信息,只有这个有用的消息。有人知道会发生什么吗?

编辑:好的,所以我把它确定为一个超出范围的异常。事实证明,我在初始化数组元素之前正在访问它们,这似乎是一种竞争条件。我有这个:

 Parallel.For(0, 4, (i, state) =>
        {
            levelTwoPermutationObjects.Add(new permutationObject());
            levelTwoPermutationObjects[i].element = number;
            levelTwoPermutationObjects[i].DoThings();
         });

这使得第二行和第三行访问一个显然还不存在的元素。我将元素初始化器移出并行循环(以便数组在被访问之前被初始化),现在它可以工作了。

迭代几乎彼此独立,除了 Add() 部分,这显然取决于它之前是否有另一个元素。

OutOfRangeException with Parallel.For

我冒着在黑暗中开枪的风险:levelTwoPermutationObjects不是线程安全的(即List<T>)。您应该改用命名空间System.Collections.Generic.Concurrent的集合,例如 ConcurrentBag<T>(因为没有线程安全版本的 List<T>),因为您正在遭受竞争条件(请参阅此处的示例)与.Add -call(在多线程中没有写入操作的读取是可以的):

public void Add(T item) {
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size++] = item;
    _version++;
}

另请参阅 MSDN 上的备注:

对 List 执行多个读取操作是安全的,但如果在读取集合时对其进行修改,则可能会出现问题。若要确保线程安全,请在读取或写入操作期间锁定集合。若要使集合能够由多个线程访问以进行读取和写入,必须实现自己的同步。有关具有内置同步的集合,请参阅 System.Collections.Radius 中的类。有关固有线程安全的替代方法,请参阅 ImmutableList 类。

如果您不愿意或无法适应levelTwoPermutationObjects类型,您还可以使用lock语句,例如(危险不使用 - 仅用于演示):

var @lock = new object();
Parallel.For(0, 4, (i, state) =>
{
    lock (@lock)
    {
        levelTwoPermutationObjects.Add(new permutationObject());
        levelTwoPermutationObjects[i].element = number;
        levelTwoPermutationObjects[i].DoThings();
    }
 });

但这会使Parallel.For调用毫无用处。实际上,您应该像(如果我正确解释您的代码)一样调整您的代码:

var @lock = new object();
Parallel.For(0, 4, (i, state) =>
{
    var permutationObject = new permutationObject
    {
        element = number
    };
    permutationObject.DoThings();
    lock (@lock)
    {
        levelTwoPermutationObjects.Add(permutationObject);
    }
 });

如果permutationObject .DoThings()是长时间运行的操作,您应该触发并忘记调用,例如 Task.Run 而不是等待结果继续进行.Add -call。

否则,您可以将处理链转换为将元素添加到集合的种子设定进程(这应该是短期运行操作)和处理进程(其中每次迭代都可以是长时间运行的操作),以避免争用条件,方法是仅在顺序写入后执行读取,例如:

var levelTwoPermutationObjects = Enumerable.Range(0, 4)
                                           .Select(arg => new permutationObject
                                                          {
                                                              element = number
                                                          })
                                           .ToList();
Parallel.ForEach(levelTwoPermutationObjects,
                 permutationObject => permutationObject.DoThings());