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 運算子產生的序列順序。 換句話說,OrderBy 和 ThenBy 等運算子的處理方式,如同後續呼叫了 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 可保證輸出元素將以遞增索引的順序產生,但不保證哪些索引會指派給哪些元素。