Parallel.For 的 OutOfRangeException

OutOfRangeException with Parallel.For

好的,所以我有一个运行正常的程序。它内部有一个可以并行化的 for 循环。所以我使用 Parallel.For 来这样做。它工作了一两次,但其他时候有以下异常:

Unspecified error One or more errors occurred

没有更多信息,只有这条有用的信息。有人知道会发生什么吗?

编辑:好的,所以我将其确定为超出范围的异常。原来我在初始化数组元素之前访问了它们,这看起来像是一个竞争条件。 我有这个:

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

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

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

我冒着摸黑开枪的风险:levelTwoPermutationObjects 不是线程安全的(即 List<T>)。您应该改用命名空间 System.Collections.Generic.Concurrent 的集合,例如 ConcurrentBag<T>(因为没有 List<T> 的线程安全版本),因为您遇到了竞争条件(请参阅 the example here) with the .Add-call(在多线程中读不写操作是可以的):

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

另见 the remarks at the MSDN:

It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it’s being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. For collections with built-in synchronization, see the classes in the System.Collections.Concurrent namespace. For an inherently thread–safe alternative, see the ImmutableList class.

如果您不愿意或不能调整 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-call 无用。实际上,您应该像这样调整您的代码(如果我正确解释了您的代码):

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());