使用Parallel.For的OutOfRangeException

use*_*604 -3 c# parallel-processing multithreading

好的,所以我有一个工作正常的程序.在其中有一个可以并行化的for循环.所以我曾经Parallel.For这样做过.它运行了一两次,但其他时候有以下异常:

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

没有进一步的信息,只有这个有用的信息 任何人都知道可能会发生什么?

编辑:好的,所以我把它钉在一个超出范围的例外.事实证明我在初始化之前访问了数组元素,这似乎是竞争条件.我有这个:

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

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

迭代几乎是彼此独立的,除了Add()部分,显然依赖于它之前是否有另一个元素.

And*_*air 5

我在黑暗中冒着风险:levelTwoPermutationObjects不是线程安全的(即是a List<T>).您应该使用的命名空间的集合System.Collections.Generic.Concurrent,如ConcurrentBag<T>(因为是没有线程安全版本List<T>),因为你是痛苦的竞争条件(请参阅这里的例子)与.Add-call(读不写操作是多线程内OK):

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

另请参阅MSDN上的评论:

在List上执行多个读取操作是安全的,但如果在读取集合时修改了集合,则可能会出现问题.要确保线程安全,请在读取或写入操作期间锁定集合.要使多个线程可以访问集合以进行读写,您必须实现自己的同步.对于具有内置同步的集合,请参阅System.Collections.Concurrent命名空间中的类.有关本质上线程安全的替代方法,请参阅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();
    }
 });
Run Code Online (Sandbox Code Playgroud)

但这会使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);
    }
 });
Run Code Online (Sandbox Code Playgroud)

如果.DoThings()permutationObject是一个长期运行的操作,你应该火了,忘记带如通话Task.Run,而不是等待结果与进行.Add-call.

否则,您可以将处理链转换为一个种子处理过程,该处理过程将元素添加到集合中(应该是一个短的运行操作)和一个处理过程(每次迭代可以是一个长时间运行的操作),以避免仅由Race条件在顺序写入之后执行读取,例如:

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