带有Parallel.For

时间:2015-06-10 19:52:27

标签: c# multithreading parallel-processing

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

  

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:5)

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

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

另见.Add-call

  

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

但这会使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);
    }
 });

如果.DoThings()的{​​{1}}是一个长时间运行的操作,您应该启动并忘记使用例如permutationObject的呼叫,而不是等待结果继续进行Task.Run } -call。

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

.Add