Parallel Extensions 的核心組件之一 PLINQ(Parallel LINQ)定位為 Task Parallel Library (TPL) 的高階應用。早期版本基於 ThreadPool 實作,未來正式版將遷移至 TPL API,但對外介面變動不大。
System.Threading.dllSystem.Linq.ParallelQuery.AsParallel 擴充方法,將資料源包裝為 IParallelEnumerableAsParallel 讓編譯器改用 System.Linq.ParallelEnumerable 的查詢運算子,而非 Enumerable。IParallelEnumerable 繼承 IEnumerable,保留 LINQ 的延遲執行特性。
public static IParallelEnumerable AsParallel(IEnumerable source)
// LINQ
string[] words = new[] { "Welcome", "to", "Beijing" };
(from word in words select Process(word)).ToArray();
// PLINQ
string[] words = new[] { "Welcome", "to", "Beijing" };
(from word in words.AsParallel() select Process(word)).ToArray();
// LINQ
var q = Enumerable.Select(Enumerable.OrderBy(
Enumerable.Where(data, x => p(x)), x => k(x)), x => f(x));
// PLINQ
var q = ParallelEnumerable.Select(ParallelEnumerable.OrderBy(
ParallelEnumerable.Where(data.AsParallel(), x => p(x)), x => k(x)), x => f(x));
二元運算子(如 Join)以最左側資料源決定是否並行,只需在第一個源呼叫 AsParallel。
查詢執行緒與枚舉執行緒分離,產生結果即輸出,不等全部完成。預設模型,優點:記憶體占用低;缺點:同步開銷較大。
枚舉執行緒等待所有查詢完成後一次輸出。ToArray、ToList、排序、聚合操作會自動切換此模型。同步開銷較小,效率稍高。可透過 GetEnumerator(false) 強制使用。
提供 Lambda 表達式,每個元素並行執行一次。效率最高,但 Lambda 內不可使用共享狀態,需用 ForAll API:
string[] words = new[] { "Welcome", "to", "Beijing", "OK", "Hua", "Ying", "Ni", "2008" };
var lazyBeeQuery = from word in words.AsParallel() select word;
lazyBeeQuery.ForAll<string>(word => { Console.WriteLine(word); });
輸出順序不固定,視核心數而定。
AsParallel(source, degreeOfParallelism) 可指定查詢執行緒數。若管道式模型需專門執行緒枚舉,可設為 Environment.ProcessorCount - 1。
並行導致順序可能改變。使用 AsParallel(ParallelQueryOptions.PreserveOrdering) 可保持原序,但會增加排序開銷,影響擴展性。注意:PreserveOrdering 對 ForAll 無效。
PLINQ 會嘗試終止所有執行緒,並將所有例外彙總至 AggregateException,透過 InnerExceptions 取得唯讀集合。