并行 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(...)
所以,我遇到了一个非常奇怪的情况,似乎 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(...)