并行 linq:AsParallel().forAll() 使某些对象为空

parallel linq: AsParallel().forAll() nulls some objects

所以,我遇到了一个非常奇怪的情况,似乎 forAll() plinq-query 删除了我的一些自定义对象,老实说,我不知道为什么。

var myArticles = data.FilterCustomerArticles([]params]).ToList(); //always returns 201 articles

result.Articles = new List<ArticleMinimal>();

try
{
    myArticles.AsParallel().ForAll(article =>
                    {
                        result.Articles.Add(new ArticleMinimal()
                        {
                            ArticleNumber = article.ArticleNumber,
                            Description = article.Description,
                            IsMaterial = false,
                            Price = article.PortionPrice.HasValue ? article.PortionPrice.Value : decimal.Zero,
                            Quantity = 1,
                            ValidFrom = new DateTime(1900, 1, 1),
                            ValidTo = new DateTime(2222, 1, 1)
                        });
                    });

}
catch (Exception ex)
{
    ...
}

上面的代码 return 几乎每次我调用它时都会产生不同的结果。 它应该return201ArticleMinimal-对象。相反,它 returns 200, 189, 19x... 并且有时是 201。没有发生异常,没有。它只是 return 比应有的对象少。

将代码更改为 "good ol'" 优雅的 foreach 循环后,我总是得到预期的 201 个对象。

工作代码:

var myArticles = data.FilterCustomerArticles([]params]).ToList(); //always returns 201 articles

result.Articles = new List<ArticleMinimal>();

try
{
    foreach (var article in myArticles) { 
        result.Articles.Add(new ArticleMinimal()
                        {
                            ArticleNumber = article.ArticleNumber,
                            Description = article.Description,
                            IsMaterial = false,
                            Price = article.PortionPrice.HasValue ? article.PortionPrice.Value : decimal.Zero,
                            Quantity = 1,
                            ValidFrom = new DateTime(1900, 1, 1),
                            ValidTo = new DateTime(2222, 1, 1)
                        });
    }

}
catch (Exception ex)
{
    ...
}

此外,在多行代码之后,我还有另一个 forAll 这样的:

try
{
    result.Articles.AsParallel().ForAll(article =>
                {
                    if (article.Weight != null){
                        ...
                    }
                });
}
catch (Exception)
{
    ...
}

使用第一个 forAll,这会抛出一个 NullReferenceException - 恕我直言,因为它需要一些 201 个对象,但有些监听是空的。

现在我真正的问题是:为什么第一个 forAll return 的对象比它应该的少?!我能想到的唯一线索是 new ArticleMinimal(){ ...}); 的内联声明——但即使这是原因,我也觉得很奇怪。使用 plinq 时不可能这样做吗?我只是在这里猜测。

希望你能帮到你。

此致, Dom

您不能从多个线程中操作 result.Articles,因为这可能会破坏内部结构,正如您观察到的那样。

而是将您的并行工作流变成一个管道,returns 创建的对象:

result.Articles.AddRange(myArticles.AsParallel().Select(article =>
    new ArticleMinimal()
    {
        ArticleNumber = article.ArticleNumber,
        Description = article.Description,
        IsMaterial = false,
        Price = article.PortionPrice.HasValue ? article.PortionPrice.Value : decimal.Zero,
        Quantity = 1,
        ValidFrom = new DateTime(1900, 1, 1),
        ValidTo = new DateTime(2222, 1, 1)
    })
);

此处的 .Select,因为它是在 ParallelQuery return 上执行的 .AsParallel() 将 运行 在项目上并行执行。

然而,.AddRange 会要求 ParallelQuery.GetEnumerator(),这将 return 将收集到的项目收集到一个长的集合中,给你想要的东西。

根本区别在于 .AddRange() 可能不会添加任何东西,直到所有并行任务开始完成,而您的方式,如果添加适当的锁定,将按原样将项目添加到集合中正在生产中。但是,除非您想观察项目在生产过程中流入集合的情况,否则这对您的情况不太可能有任何意义。

List.Add 不是线程安全的。请参考

使用任一锁

lock (result.Articles)
{
    result.Articles.Add(...);
}

或者一个线程安全的集合。我会使用一个临时集合,最后使用 result.Articles.AddRange(...)