PLINQ 中的顺序保留
在 PLINQ 中,目标是在保持正确性的同时,最大限度地提升性能。 虽然查询应尽可能快地运行,但仍应生成正确结果。 在某些情况下,为了满足正确性要求,必须暂留源序列的顺序;不过,顺序暂留的计算成本可能非常高。 因此,默认情况下,PLINQ 不暂留源序列的顺序。 在这方面,PLINQ 与 LINQ to SQL 类似,但与 LINQ to Objects 不同,后者确实会暂留顺序。
若要替代默认行为,可以对源序列使用 AsOrdered 运算符,启用顺序暂留。 稍后,可以使用 AsUnordered 方法,在查询中禁用顺序暂留。 使用这两种方法时,查询的处理依据为,确定是并行执行还是顺序执行查询的启发。 有关详细信息,请参阅了解 PLINQ 中的加速。
下面的示例展示了无序并行查询,以筛选符合条件的所有元素,而完全不尝试对结果进行排序。
var cityQuery =
(from city in cities.AsParallel()
where city.Population > 10000
select city).Take(1000);
Dim cityQuery = From city In cities.AsParallel()
Where city.Population > 10000
Take (1000)
此查询不一定会生成源序列中符合条件的前 1000 个城市,而会生成符合条件的 1000 个城市中的一部分。 PLINQ 查询运算符将源序列划分为多个子序列,并将它们作为并发任务进行处理。 如果未指定顺序暂留,每个分区生成的结果以任意顺序传递到查询的下一个阶段。 此外,在继续处理其余元素前,分区可能还会生成一部分结果。 因此,顺序可能每次都不同。 应用无法对此进行控制,因为它取决于操作系统如何将线程排入计划。
下面的示例对源序列使用 AsOrdered 运算符,以替代默认行为。 这样可确保 Take 方法返回源序列中符合条件的前 1000 个城市。
var orderedCities =
(from city in cities.AsParallel().AsOrdered()
where city.Population > 10000
select city).Take(1000);
Dim orderedCities = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Take (1000)
不过,此查询可能不会像无序版本那样快速运行,因为它必须跟踪整个分区中的原始顺序,并且在合并时确保顺序是一致的。 因此,建议仅在需要时,才将 AsOrdered用于需要它的查询部分。 如果不再需要顺序暂留,请使用 AsUnordered 禁用它。 为此,下面的示例撰写了两个查询。
var orderedCities2 =
(from city in cities.AsParallel().AsOrdered()
where city.Population > 10000
select city).Take(1000);
var finalResult =
from city in orderedCities2.AsUnordered()
join p in people.AsParallel()
on city.Name equals p.CityName into details
from c in details
select new
{
city.Name,
Pop = city.Population,
c.Mayor
};
foreach (var city in finalResult) { /*...*/ }
Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Select city
Take (1000)
Dim finalResult = From city In orderedCities2.AsUnordered()
Join p In people.AsParallel() On city.Name Equals p.CityName
Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}
For Each city In finalResult
Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next
请注意,PLINQ 暂留查询其余部分的顺序强制施加运算符生成的序列顺序。 也就是说,OrderBy 和 ThenBy 等运算符被视为后跟 AsOrdered 调用。
查询运算符和顺序
下面的查询运算符将顺序暂留引入查询中的所有后续操作,或一直运行到 AsUnordered 获得调用:
在某些情况下,下面的 PLINQ 查询运算符可能需要有序的源序列,才能生成正确结果:
一些 PLINQ 查询运算符的行为因源序列是有序还是无序而异。 下表列出了这些运算符。
运算符 | 源序列为有序时的结果 | 源序列为无序时的结果 |
---|---|---|
Aggregate | 非关联或非交换操作的非确定性输出 | 非关联或非交换操作的非确定性输出 |
All | 不适用 | 不适用 |
Any | 不适用 | 不适用 |
AsEnumerable | 不适用 | 不适用 |
Average | 非关联或非交换操作的非确定性输出 | 非关联或非交换操作的非确定性输出 |
Cast | 有序结果 | 无序结果 |
Concat | 有序结果 | 无序结果 |
Count | 不适用 | 不适用 |
DefaultIfEmpty | 不适用 | 不适用 |
Distinct | 有序结果 | 无序结果 |
ElementAt | 返回指定元素 | 任意元素 |
ElementAtOrDefault | 返回指定元素 | 任意元素 |
Except | 无序结果 | 无序结果 |
First | 返回指定元素 | 任意元素 |
FirstOrDefault | 返回指定元素 | 任意元素 |
ForAll | 非确定性并行执行 | 非确定性并行执行 |
GroupBy | 有序结果 | 无序结果 |
GroupJoin | 有序结果 | 无序结果 |
Intersect | 有序结果 | 无序结果 |
Join | 有序结果 | 无序结果 |
Last | 返回指定元素 | 任意元素 |
LastOrDefault | 返回指定元素 | 任意元素 |
LongCount | 不适用 | 不适用 |
Min | 不适用 | 不适用 |
OrderBy | 对序列重新排序 | 开始新的有序部分 |
OrderByDescending | 对序列重新排序 | 开始新的有序部分 |
Range | 暂无(默认值与 AsParallel 相同) | 不适用 |
Repeat | 暂无(默认值与 AsParallel 相同) | 不适用 |
Reverse | 反转 | 不执行任何操作 |
Select | 有序结果 | 无序结果 |
Select(已编入索引) | 有序结果 | 无序结果。 |
SelectMany | 有序结果。 | 无序结果 |
SelectMany(已编入索引) | 有序结果。 | 无序结果。 |
SequenceEqual | 有序比较 | 无序比较 |
Single | 不适用 | 不适用 |
SingleOrDefault | 不适用 | 不适用 |
Skip | 跳过第一个 n 元素 | 跳过所有 n 元素 |
SkipWhile | 有序结果。 | 非确定性。 以当前任意顺序执行 SkipWhile |
Sum | 非关联或非交换操作的非确定性输出 | 非关联或非交换操作的非确定性输出 |
Take | 获取前 n 个元素 |
获取任意 n 个元素 |
TakeWhile | 有序结果 | 非确定性。 以当前任意顺序执行 TakeWhile |
ThenBy | 补充 OrderBy |
补充 OrderBy |
ThenByDescending | 补充 OrderBy |
补充 OrderBy |
ToArray | 有序结果 | 无序结果 |
ToDictionary | 不适用 | 不适用 |
ToList | 有序结果 | 无序结果 |
ToLookup | 有序结果 | 无序结果 |
Union | 有序结果 | 无序结果 |
Where | 有序结果 | 无序结果 |
Where(已编入索引) | 有序结果 | 无序结果 |
Zip | 有序结果 | 无序结果 |
无序结果并不是主动随机无序;就是没有应用任何特殊顺序逻辑。 在某些情况下,无序查询可能会暂留源序列的顺序。 对于使用已编入索引的 Select 运算符的查询,PLINQ 保证输出元素按索引升序排列,但不保证向元素分配哪些索引。