c# - 具有共享函数的 C# Parallel.ForEach 抛出 IndexOutOfRangeException
问题描述
我需要帮助解决 Parallel.ForEach 中共享函数的问题。我得到了一个错误,如何更改要保存的函数以使用线程?
public IEnumerable<Datamodel> LoadLibrary(IEnumerable<Items> items)
{
var allLibReferences = new List<LibraryReferenceModel>();
var baseData = LoadBaseLibData();
Parallel.ForEach(baseData, data =>
{
var item = items.ToList().FindAll(c => c.Name == data.Name);
CreateLibraryReference(allLibReferences, item, data.Name); // Problem to call function in Parallel.ForEach
});
return allLibReferences;
}
private static void CreateLibraryReference(ICollection<LibraryReferenceModel> allLibReferences,
IReadOnlyCollection<Item> item, string libraryName)
{
allLibReferences.Add(item.Count == 0
? new LibraryReferenceModel
{
LibName = libraryName,
HasReference = false,
References = item.Count
}
: new LibraryReferenceModel
{
LibName = libraryName,
HasReference = true,
References = item.Count
});
}
我得到了这个异常(索引超出了数组范围):
谢谢
解决方案
正如您所发现的,由于多个线程正在尝试将新项目添加到共享allLibReferences
集合中,您会发现不稳定的线程安全问题,例如您所描述的错误。
这就是为什么在考虑并行化代码之前确保代码线程安全非常重要的原因。最好的技术之一是确保您依赖于不可变的代码结构,即在并行代码期间永远不要尝试更改(改变)共享变量的值。
所以我会改变代码的工作方式,而不是共享一个集合,我们所做的是不可变地投影所需的项目,这可以安全地并行化(我使用过.AsParallel
,因为它更简单),然后你可以整理结果并返回。
此外,由于并行性的全部意义在于使代码尽可能快地运行,因此您还希望消除低效率,例如在每次迭代期间将相同的项目具体化(items.ToList()
),并且您还希望避免O(N)
迭代如果可能,在循环期间 - 我已替换.FindAll(c => c.Name == data.Name)
为预先计算的字典。
总而言之,你会得到这样的结果:
public IEnumerable<LibraryReferenceModel> LoadLibrary(IEnumerable<Item> items)
{
var keyedItems = items.GroupBy(i => i.Name)
.ToDictionary(grp => grp.Key, grp => grp.ToList());
var baseData = LoadBaseLibData();
var allLibReferences = baseData
.AsParallel()
.SelectMany(data =>
{
if (keyedItems.TryGetValue(data.Name, out var matchedItems))
{
return matchedItems
.Select(i => ProjectLibraryReference(i, data.Name));
}
// No matches found
return new LibraryReferenceModel
{
LibName = data.Name,
HasReference = false,
References = 0
};
})
.ToList();
return allLibReferences;
}
private static LibraryReferenceModel ProjectLibraryReference(IReadOnlyCollection<Item> item,
string libraryName)
{
return new LibraryReferenceModel
{
LibName = libraryName,
HasReference = item.Count > 0,
References = item.Count
};
}
我假设多个项目可以具有相同的名称,因此我们在创建字典之前进行分组,然后我们是flattening
最后的投影结果.SelectMany
。
推荐阅读
- ios - 如何制作一个 uicollectionview,它可以在单击按钮时添加新单元格,并且每个单元格的高度都可以调整,并且高度保持不变
- javascript - reactJS 状态数组对象 - 如何在 setState() 中引用状态数组对象
- swift - 为什么会缓存已删除的 tableView 部分的属性?
- django - 基于共享字段组合两个查询集
- javascript - 在函数内部返回函数如何工作
- excel - 使用 Maatwebsite 导出 excel 文件时为 foreach() 提供的参数无效
- hadoop - Hadoop 调度程序与 oozie
- python - Tkinter - 在一个类中使用 Button 命令从另一个类调用函数
- c++ - 使用模板方法为标头提供空 cpp 的问题
- webpack - 为什么 Webpack 中没有设置环境