4

我的代码做了非常简单的事情

列表已经有元素。我在列表中有大约 25000 个元素(我希望有更多),每个元素都很小(DateTime)。

List<DateTime> newList = new List<DateTime>();
Parallel.ForEach(list, l => newlist.Add(new DateTime(l.Ticks + 5000)));

即,基于每个元素,我正在创建新元素并将它们添加到不同的列表中。但是,这似乎不是一个好的编程方法。我有时会遇到此异常,但不是每次都遇到这种情况。

IndexOutOfRangeException : {"Index was outside the bounds of the array."}

我们可以使用 Parallel.ForEach() 将元素添加到列表中吗?如果是,为什么我会遇到错误?如果不是,为什么?

4

6 回答 6

6

在这种情况下你真正想要的更像是这样的:

newlist = list.AsParallel().Select(l => new DateTime(l.Ticks + 5000)).ToList();

尽管您应该测量性能以查看这种情况是否甚至可以从并行化中受益。

于 2012-04-13T23:43:13.743 回答
5

尝试使用最终结果的线程局部变量,将所有线程局部变量添加到 newList 中......

Parallel.ForEach(list, () => DateTime.MinValue, (l, state, date) =>
{
    date = new DateTime(l.Ticks+5000);
    return date;
},
finalresult =>
{
   lock (newList)
   {
       newList.Add(finalresult);
   }
});

第一个参数是您的旧列表,第二个参数是每个线程的初始值(我刚刚初始化为 datetime min)。第三个参数块如下 - l 与您的代码相同;state 是一个 Paralleloption 对象,如果您选择,您可以退出并行循环;最后一个是代表线程局部变量的stand in 变量。finalresult 参数代表每个线程局部变量的最终结果,并为每个线程调用 - 在那里您可以放置​​ newList 的锁并添加到 newList 共享变量。理论上这是可行的。我在自己的代码中使用了类似的编码。希望这对您或其他人有所帮助。

于 2012-05-30T02:57:44.237 回答
2

正如每个人都提到的那样,似乎没有理由这样做。它肯定会慢得多,慢得多。但是,为了完成,有时会失败的原因是多个线程正在写入的列表对象上没有锁定。添加这个:

object _locker = new object();
List<DateTime> newList = new List<DateTime>();
Parallel.ForEach(list, l => lock (_locker) newlist.Add(new DateTime(l.Ticks + 5000)));
于 2012-04-13T23:30:24.420 回答
2

这将有效地List<T>.Add同时调用,但根据MSDN 文档List<T>

“不保证任何实例成员都是线程安全的。”

即使它是(线程安全的),这也太便宜而无法从并行执行中受益(与并行执行的开销相反)。你真的衡量过你的表现吗?25000 个元素并不

于 2012-04-13T23:16:18.227 回答
1

根本没有足够的工作来保证使用Parallel.ForEach并且也不List<T>是线程安全的,因此如果您想并行添加到同一个列表中,则必须锁定。只需使用常规 for 循环。

于 2012-04-13T23:11:00.300 回答
1

你真的需要这些在列表中吗?如果您只需要在 foreach 中枚举列表,您可能应该这样做,因为它将使用更少的内存:

IEnumerable<DateTime> newSequence = list.Select(d => new DateTime(d.Ticks + 5000));

如果您真的需要这些列表,只需在末尾添加 .ToList() :

var newSequence = list.Select(d => new DateTime(d.Ticks + 5000)).ToList();

这几乎肯定会足够快,您不需要并行化它。事实上,这可能比并行执行要快,因为它具有更好的内存性能。

于 2012-04-13T23:16:59.017 回答