PLINQ 中的顺序保留

在 PLINQ 中,目标是在保持正确性的同时最大程度地提升性能。 查询应尽快运行,但仍然应生成正确的结果。 在某些情况下,正确性要求源序列的顺序得以保留;但是,排序可能会消耗大量的计算资源。 因此,在默认情况下,PLINQ 不会保留源序列的顺序。 就这一点而言,PLINQ 类似于 LINQ to SQL,但与会保留排序的 LINQ to Objects 不同。

若要重写默认行为,您可以通过对源序列使用 AsOrdered 运算符来启用顺序保留。 然后,您可以稍后通过使用 AsUnordered<TSource> 方法在查询中禁用顺序保留。 对于这两个方法,将基于确定以并行还是顺序方式执行查询的探索处理查询。 有关更多信息,请参见了解 PLINQ 中的加速

下面的示例演示一个未排序的并行查询,该查询筛选与某个条件匹配的所有元素,而不会尝试以任何方式对结果进行排序。

Dim cityQuery = From city In cities.AsParallel()
               Where City.Population > 10000
               Take (1000)
var cityQuery = (from city in cities.AsParallel()
                 where city.Population > 10000
                 select city)
                   .Take(1000);

此查询不一定会生成源序列中满足条件的前 1000 个城市,而是会生成满足条件的某一组 1000 个城市。 PLINQ 查询运算符会将源序列分区为作为并发任务处理的多个子序列。 如果未指定顺序保留,则会按任意顺序将每个分区中的结果传递给查询的下一阶段。 此外,分区可能会在继续处理其余元素之前生成其结果的子集。 产生的顺序每次可能都不同。 您的应用程序无法控制这一点,因为它依赖于操作系统调度线程的方式。

下面的示例通过对源序列使用 AsOrdered 运算符来重写默认行为。 这将确保 Take<TSource> 方法返回源序列中满足条件的前 10 个城市。

Dim orderedCities = From city In cities.AsParallel().AsOrdered()
                    Where City.Population > 10000
                    Take (1000)
            var orderedCities = (from city in cities.AsParallel().AsOrdered()
                                 where city.Population > 10000
                                 select city)
                                .Take(1000);

但是,此查询的运行速度可能不如未排序版本快,因为它必须在各个分区中跟踪原始排序,并在合并时确保排序是一致的。 因此,我们建议您仅在必需时才使用 AsOrdered,并且仅为需要它的那些查询部分使用。 不再需要顺序保留时,请使用 AsUnordered<TSource> 将其禁用。 下面的示例通过编写两个查询来实现这一点。

        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

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 { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

请注意,PLINQ 保留由查询其余部分的顺序限制运算符生成的序列排序。 换言之,诸如 OrderByThenBy 等运算符将被视为好像在它们之后调用了 AsOrdered

查询运算符和排序

下面的查询运算符将顺序保留引入查询的所有后续运算中,或直至调用 AsUnordered<TSource> 为止:

下面的 PLINQ 查询运算符在某些情况下可能要求经过排序的源序列生成正确的结果:

某些 PLINQ 查询运算符的行为有所不同,具体情况取决于其源序列是否已经过排序。 下表列出了这些运算符。

运算符

在源序列经过排序时生成

在源序列未经过排序时生成

Aggregate

不可结合或不可交换的操作的不确定输出

不可结合或不可交换的操作的不确定输出

All<TSource>

不适用

不适用

Any

不适用

不适用

AsEnumerable<TSource>

不适用

不适用

Average

不可结合或不可交换的操作的不确定输出

不可结合或不可交换的操作的不确定输出

Cast<TResult>

经过排序的结果

未经过排序的结果

Concat

经过排序的结果

未经过排序的结果

Count

不适用

不适用

DefaultIfEmpty

不适用

不适用

Distinct

经过排序的结果

未经过排序的结果

ElementAt<TSource>

返回指定的元素

任意元素

ElementAtOrDefault<TSource>

返回指定的元素

任意元素

Except

未经过排序的结果

未经过排序的结果

First

返回指定的元素

任意元素

FirstOrDefault

返回指定的元素

任意元素

ForAll<TSource>

以非确定性的方式并行执行

以非确定性的方式并行执行

GroupBy

经过排序的结果

未经过排序的结果

GroupJoin

经过排序的结果

未经过排序的结果

Intersect

经过排序的结果

未经过排序的结果

Join

经过排序的结果

未经过排序的结果

Last

返回指定的元素

任意元素

LastOrDefault

返回指定的元素

任意元素

LongCount

不适用

不适用

Min

不适用

不适用

OrderBy

对序列重新排序

开始新的经过排序的节

OrderByDescending

对序列重新排序

开始新的经过排序的节

Range

不适用(与 AsParallel 相同的默认值)

不适用

Repeat<TResult>

不适用(与 AsParallel 相同的默认值)

不适用

Reverse<TSource>

反转

不执行任何操作

Select

经过排序的结果

未经过排序的结果

Select(已编制索引)

经过排序的结果

未经过排序的结果。

SelectMany

经过排序的结果。

未经过排序的结果

SelectMany(已编制索引)

经过排序的结果。

未经过排序的结果。

SequenceEqual

经过排序的比较

未经过排序的比较

Single

不适用

不适用

SingleOrDefault

不适用

不适用

Skip<TSource>

跳过前 n 个元素

跳过任意 n 个元素

SkipWhile

经过排序的结果。

不确定。 对当前的任意顺序执行 SkipWhile

Sum

不可结合或不可交换的操作的不确定输出

不可结合或不可交换的操作的不确定输出

Take<TSource>

采用前 n 个元素

采用任意 n 个元素

TakeWhile

经过排序的结果

不确定。 对当前的任意顺序执行 TakeWhile

ThenBy

补充 OrderBy

补充 OrderBy

ThenByDescending

补充 OrderBy

补充 OrderBy

ToTSource[]

经过排序的结果

未经过排序的结果

ToDictionary

不适用

不适用

ToList<TSource>

经过排序的结果

未经过排序的结果

ToLookup

经过排序的结果

未经过排序的结果

Union

经过排序的结果

未经过排序的结果

Where

经过排序的结果

未经过排序的结果

Where(已编制索引)

经过排序的结果

未经过排序的结果

Zip

经过排序的结果

未经过排序的结果

不会主动对未经排序的结果进行随机排序;它们只是未应用任何特殊排序逻辑。 在某些情况下,未经排序的查询可以保留源序列的排序。 对于使用已编制索引的 Select 运算符的查询,PLINQ 保证输出元素将按索引增加的顺序出现,但不保证将哪个索引分配给哪个元素。

请参见

概念

并行 LINQ (PLINQ)

.NET Framework 中的并行编程