在 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 保证输出元素将按增加索引的顺序显示,但不保证将哪些索引分配给哪些元素。