使用Parallel.Foreach批量创建对象仍耗时过长,求优化方案
嘿,4000条记录创建对象耗时超8秒确实有点反常,咱们来一步步拆解优化点,帮你把时间压下来:
抛弃ConcurrentBag,改用PLINQ或预分配容量的List
ConcurrentBag虽然是线程安全集合,但它的锁开销在并行创建对象场景下其实没必要——PLINQ的AsParallel().Select().ToList()会自动高效处理并行结果的收集,不需要手动维护线程安全集合。另外如果串行处理更高效(小数据集有时候并行调度开销反而更大),直接用预分配容量的List<data>(比如new List<data>(allRows.Count))也能避免List动态扩容的开销。示例(PLINQ版本):
var lstdata = allRows.AsParallel() .Select(row => new data { Id = row["NB"], ColumnId = row["COLUMN_ID"], Value = row["VALUE"] }) .ToList();优化行数据的访问方式
如果你用的是DataRow这类对象,字符串索引器(row["NB"])每次都会做列名查找,开销很大。建议提前获取列的索引,用索引访问来替代:// 假设allRows里的元素是DataRow,提前获取列索引 var sampleRow = (DataRow)allRows[0]; int nbColIndex = sampleRow.Table.Columns["NB"].Ordinal; int columnIdColIndex = sampleRow.Table.Columns["COLUMN_ID"].Ordinal; int valueColIndex = sampleRow.Table.Columns["VALUE"].Ordinal; // 之后用索引访问 var lstdata = allRows.AsParallel() .Select(row => { var dataRow = (DataRow)row; return new data { Id = dataRow[nbColIndex], ColumnId = dataRow[columnIdColIndex], Value = dataRow[valueColIndex] }; }) .ToList();减少装箱拆箱开销
如果row中的字段是值类型(比如int、decimal),直接用row[...]会返回object,赋值给data属性时会发生拆箱。可以用强类型访问方法(比如DataRow的Field<T>)来避免:// 假设Id是int类型,Value是string类型 return new data { Id = dataRow.Field<int>(nbColIndex), ColumnId = dataRow.Field<int>(columnIdColIndex), Value = dataRow.Field<string>(valueColIndex) };检查Data类的额外开销
看看你的data类的属性setter里有没有隐藏逻辑(比如数据验证、INotifyPropertyChanged通知),这些在批量创建时会累积大量开销。如果可以,尽量把这类逻辑延迟到批量创建完成后再执行。另外,若data是类,也可以考虑改用结构体(值类型),但要注意后续使用场景的复制开销,先测试再决定。测试并行vs串行的性能差异
4000条数据其实不算特别大,Parallel.ForEach的线程调度开销可能超过并行带来的收益。可以试试串行版本,对比耗时:var lstdata = new List<data>(allRows.Count); foreach (var row in allRows) { var dataRow = (DataRow)row; lstdata.Add(new data { Id = dataRow.Field<int>(nbColIndex), ColumnId = dataRow.Field<int>(columnIdColIndex), Value = dataRow.Field<string>(valueColIndex) }); }
建议先从替换ConcurrentBag和优化行访问这两点入手,这两个是最容易见效的优化项。
内容的提问来源于stack exchange,提问作者Miz

