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 運算子所產生的序列順序。 換句話說,OrderBy 和 ThenBy 之類的運算子,會被視為後續接著會有 AsOrdered 呼叫的運算子。
查詢運算子和順序
下列查詢運算子會在查詢的所有後續作業中執行順序保留,或在呼叫 AsUnordered<TSource> 後執行:
下列 PLINQ 查詢運算子在某些情況下可能要有排序的來源序列,才能產生正確的結果:
有些 PLINQ 查詢運算子的行為會隨著其來源序列排序與否而不同。 這些運算子如下表所列。
運算子 |
來源序列已排序的結果 |
來源序列未排序的結果 |
---|---|---|
不具關聯性或不具互換性之作業的不具決定性輸出 |
不具關聯性或不具互換性之作業的不具決定性輸出 |
|
不適用 |
不適用 |
|
不適用 |
不適用 |
|
不適用 |
不適用 |
|
不具關聯性或不具互換性之作業的不具決定性輸出 |
不具關聯性或不具互換性之作業的不具決定性輸出 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
不適用 |
不適用 |
|
不適用 |
不適用 |
|
排序結果 |
未排序結果 |
|
傳回指定的項目 |
任意項目 |
|
傳回指定的項目 |
任意項目 |
|
未排序結果 |
未排序結果 |
|
傳回指定的項目 |
任意項目 |
|
傳回指定的項目 |
任意項目 |
|
非決定性地平行執行 |
非決定性地平行執行 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
傳回指定的項目 |
任意項目 |
|
傳回指定的項目 |
任意項目 |
|
不適用 |
不適用 |
|
不適用 |
不適用 |
|
重新排序序列 |
啟動新的排序區段 |
|
重新排序序列 |
啟動新的排序區段 |
|
不適用 (與 AsParallel 相同的預設值) |
不適用 |
|
不適用 (與 AsParallel 相同的預設值) |
不適用 |
|
反轉 |
不執行任何動作 |
|
排序結果 |
未排序結果 |
|
Select (已編排索引) |
排序結果 |
未排序結果 |
排序結果 |
未排序結果 |
|
SelectMany (已編排索引) |
排序結果 |
未排序結果 |
排序比較 |
未排序比較 |
|
不適用 |
不適用 |
|
不適用 |
不適用 |
|
略過前 n 個項目 |
略過任 n 個項目 |
|
排序結果 |
非決定性。 以目前的任意順序執行 SkipWhile |
|
不具關聯性或不具互換性之作業的不具決定性輸出 |
不具關聯性或不具互換性之作業的不具決定性輸出 |
|
取用前 n 個項目 |
取用任 n 個項目 |
|
排序結果 |
非決定性。 以目前的任意順序執行 TakeWhile |
|
補充 OrderBy |
補充 OrderBy |
|
補充 OrderBy |
補充 OrderBy |
|
排序結果 |
未排序結果 |
|
不適用 |
不適用 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
排序結果 |
未排序結果 |
|
Where (已編排索引) |
排序結果 |
未排序結果 |
排序結果 |
未排序結果 |
未排序結果不是主動隨機排列,而是未套用任何特殊排序邏輯。 在某些情況中,未排序的查詢可能保有來源序列順序。 對於使用索引 Select 運算子的查詢,PLINQ 保證輸出項目會依遞增索引順序顯示,但不保證哪個索引會指派給項目。