首页 > 解决方案 > 具有共享函数的 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
                });
        }

我得到了这个异常(索引超出了数组范围):

在此处输入图像描述

谢谢

标签: c#asp.netasp.net-mvc

解决方案


正如您所发现的,由于多个线程正在尝试将新项目添加到共享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


推荐阅读