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 會為其餘查詢保留 order-imposing 運算子所產生的序列順序。 換句話說,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 進行平行程式設計