Share via


PLINQ 中的順序保留

在 PLINQ 中,目標是在達到最佳效能的同時維持正確性。 查詢應以最快的速度執行,但仍應產生正確的結果。 在某些情況下,需要保留來源序列的順序以保持正確性;不過,排序可能需要大量計算。 因此,根據預設,PLINQ 不會保留來源序列的順序。 在此方面,PLINQ 類似於 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 會為其餘的查詢保留 order-imposing 運算子產生的序列順序。 換句話說,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 可保證輸出元素將以遞增索引的順序產生,但不保證哪些索引會指派給哪些元素。

另請參閱