好的,所以我有一个工作正常的程序。在其中有一个可以并行化的for循环。所以我使用Parallel.For
来做到这一点。它运行了一两次,但其他时候有以下例外:
未指定的错误发生了一个或多个错误
没有进一步的信息,只是这个有用的信息。任何人都知道可能会发生什么?
编辑:好的,所以我把它钉在了超出范围的异常中。事实证明我在初始化之前访问了数组元素,这似乎是竞争条件。 我有这个:
Parallel.For(0, 4, (i, state) =>
{
levelTwoPermutationObjects.Add(new permutationObject());
levelTwoPermutationObjects[i].element = number;
levelTwoPermutationObjects[i].DoThings();
});
这使得第二行和第三行访问一个显然尚不存在的元素。我将元素初始化程序移出并行循环(以便在访问之前初始化数组),现在它可以工作。
迭代几乎是彼此独立的,除了Add()部分,显然依赖于它之前是否有另一个元素。
答案 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