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 会保留由排序运算符生成的序列在查询其余部分中的顺序。 也就是说,OrderByThenBy 等运算符被视为后跟 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 保证输出元素将按增加索引的顺序显示,但不保证将哪些索引分配给哪些元素。

另请参阅