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 保证输出元素按索引升序排列,但不保证向元素分配哪些索引。

请参阅