PLINQ 簡介Introduction to PLINQ

何謂平行查詢?What is a Parallel Query?

Language-Integrated Query (LINQ) 是在 .NET Framework 3.5 中引進的。Language-Integrated Query (LINQ) was introduced in the .NET Framework 3.5. 其特色是以型別安全方法查詢任何 System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T> 資料來源的統一模型。It features a unified model for querying any System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> data source in a type-safe manner. LINQ to Objects 是 LINQ 查詢的名稱,它是針對記憶體中的集合 (例如 List<T> 和陣列) 執行。LINQ to Objects is the name for LINQ queries that are run against in-memory collections such as List<T> and arrays. 本文假設您已對 LINQ 有基本了解。This article assumes that you have a basic understanding of LINQ. 如需詳細資訊,請參閱 Language-Integrated Query (LINQ) - C# Language-Integrated Query (LINQ) - Visual BasicFor more information, see Language-Integrated Query (LINQ) - C# or Language-Integrated Query (LINQ) - Visual Basic.

Parallel LINQ (PLINQ) 是平行實作的 LINQ 模式。Parallel LINQ (PLINQ) is a parallel implementation of the LINQ pattern. PLINQ 查詢在很多方面類似於非平行 LINQ to Objects 查詢。A PLINQ query in many ways resembles a non-parallel LINQ to Objects query. PLINQ 查詢就如同連續的 LINQ 查詢,會在任何記憶體中的 IEnumerableIEnumerable<T> 資料來源上運作,並具有延後執行,這表示在列舉查詢之前,它們不會開始執行。PLINQ queries, just like sequential LINQ queries, operate on any in-memory IEnumerable or IEnumerable<T> data source, and have deferred execution, which means they do not begin executing until the query is enumerated. 這兩種查詢的主要差別在於,PLINQ 會嘗試充分利用系統上的所有處理器。The primary difference is that PLINQ attempts to make full use of all the processors on the system. 它所採取的方式是將資料來源分割成多個區段,然後在多個處理器上,以平行方式在不同的背景工作執行緒上對每個區段執行查詢。It does this by partitioning the data source into segments, and then executing the query on each segment on separate worker threads in parallel on multiple processors. 在許多情況下,平行執行意謂著查詢的執行速度會明顯加快。In many cases, parallel execution means that the query runs significantly faster.

透過平行執行,PLINQ 就能對適用於某些查詢種類的舊版程式碼,達成顯著的效能改善,而這通常只需要對資料來源新增 AsParallel 作業即可做到。Through parallel execution, PLINQ can achieve significant performance improvements over legacy code for certain kinds of queries, often just by adding the AsParallel query operation to the data source. 不過,平行處理原則也會帶來自己的複雜性,所以並非所有查詢作業都可透過 PLINQ 加快執行速度。However, parallelism can introduce its own complexities, and not all query operations run faster in PLINQ. 事實上,平行處理的確會降低某些查詢的速度。In fact, parallelization actually slows down certain queries. 因此,您應該了解各種問題 (例如排序) 會如何影響平行查詢。Therefore, you should understand how issues such as ordering affect parallel queries. 如需詳細資訊,請參閱認識 PLINQ 中的加速For more information, see Understanding Speedup in PLINQ.

注意

本文件使用 Lambda 運算式來定義 PLINQ 中的委派。This documentation uses lambda expressions to define delegates in PLINQ. 如果您不熟悉 C# 或 Visual Basic 中的 Lambda 運算式,請參閱 PLINQ 和 TPL 中的 Lambda 運算式If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda Expressions in PLINQ and TPL.

本文其餘部分會概述 PLINQ 的主要類別,並討論如何建立 PLINQ 查詢。The remainder of this article gives an overview of the main PLINQ classes, and discusses how to create PLINQ queries. 每一節都附有連結,可供您獲得詳細資訊和程式碼範例。Each section contains links to more detailed information and code examples.

ParallelEnumerable 類別The ParallelEnumerable Class

System.Linq.ParallelEnumerable 類別會公開幾乎所有的 PLINQ 功能。The System.Linq.ParallelEnumerable class exposes almost all of PLINQ's functionality. 此類別和其餘的 System.Linq 命名空間型別會編譯成 System.Core.dll 組件。It and the rest of the System.Linq namespace types are compiled into the System.Core.dll assembly. Visual Studio 中預設的 C# 和 Visual Basic 專案都會參考此組件並匯入該命名空間。The default C# and Visual Basic projects in Visual Studio both reference the assembly and import the namespace.

ParallelEnumerable 會實作 LINQ to Objects 所支援的所有標準查詢運算子,但不會嘗試平行處理每個運算子。ParallelEnumerable includes implementations of all the standard query operators that LINQ to Objects supports, although it does not attempt to parallelize each one. 如果您不熟悉 linq,請參閱linq 簡介(C#)linq 簡介(Visual Basic)If you are not familiar with LINQ, see Introduction to LINQ (C#) and Introduction to LINQ (Visual Basic).

除了標準查詢運算子外,ParallelEnumerable 類別還會包含一組方法,以供啟用平行執行特有的行為。In addition to the standard query operators, the ParallelEnumerable class contains a set of methods that enable behaviors specific to parallel execution. 下表列出這些 PLINQ 特有的方法。These PLINQ-specific methods are listed in the following table.

ParallelEnumerable 運算子ParallelEnumerable Operator 描述Description
AsParallel PLINQ 的進入點。The entry point for PLINQ. 指定系統應該在情況允許時平行處理其餘查詢。Specifies that the rest of the query should be parallelized, if it is possible.
AsSequential 指定系統應該將其餘查詢當作非平行 LINQ 查詢來循序執行。Specifies that the rest of the query should be run sequentially, as a non-parallel LINQ query.
AsOrdered 指定 PLINQ 應該為其餘查詢保留來源序列的排序,或保留到排序變更時,例如透過使用 orderby (在 Visual Basic 中是 Order By) 子句來變更。Specifies that PLINQ should preserve the ordering of the source sequence for the rest of the query, or until the ordering is changed, for example by the use of an orderby (Order By in Visual Basic) clause.
AsUnordered 指定用於其餘查詢的 PLINQ 不需要保留來源序列的排序。Specifies that PLINQ for the rest of the query is not required to preserve the ordering of the source sequence.
WithCancellation 指定 PLINQ 應定期監視所提供之取消權杖的狀態,並在經過要求後取消執行。Specifies that PLINQ should periodically monitor the state of the provided cancellation token and cancel execution if it is requested.
WithDegreeOfParallelism 指定 PLINQ 應該用來平行處理查詢的處理器數目上限。Specifies the maximum number of processors that PLINQ should use to parallelize the query.
WithMergeOptions 在情況允許時提供提示,說明 PLINQ 應該如何將平行結果合併回取用者執行緒上的單一序列中。Provides a hint about how PLINQ should, if it is possible, merge parallel results back into just one sequence on the consuming thread.
WithExecutionMode 指定 PLINQ 是否應在預設行為是循序執行查詢時,仍平行處理查詢。Specifies whether PLINQ should parallelize the query even when the default behavior would be to run it sequentially.
ForAll 多執行緒列舉方法不同於逐一查看查詢結果的方法,前者可平行處理所有結果,而不必先合併回取用者執行緒。A multithreaded enumeration method that, unlike iterating over the results of the query, enables results to be processed in parallel without first merging back to the consumer thread.
Aggregate 多載Aggregate overload 這是 PLINQ 特有的多載,可對執行緒區域分割啟用中繼彙總,並附有最後彙總函式可結合所有分割的結果。An overload that is unique to PLINQ and enables intermediate aggregation over thread-local partitions, plus a final aggregation function to combine the results of all partitions.

加入模型The Opt-in Model

當您撰寫完查詢時,請藉由對資料來源叫用 ParallelEnumerable.AsParallel 擴充方法來將查詢加入 PLINQ,如下列範例所示。When you write a query, opt in to PLINQ by invoking the ParallelEnumerable.AsParallel extension method on the data source, as shown in the following example.

var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
               where num % 2 == 0
               select num;
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count());
// The example displays the following output:
//       5000 even numbers out of 10000 total      
Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
               Where num Mod 2 = 0
               Select num
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count())
' The example displays the following output:
'       5000 even numbers out of 10000 total      

AsParallel 擴充方法會將後續的查詢運算子 (此案例中為 whereselect) 繫結至 System.Linq.ParallelEnumerable 實作。The AsParallel extension method binds the subsequent query operators, in this case, where and select, to the System.Linq.ParallelEnumerable implementations.

執行模式Execution Modes

根據預設,PLINQ 會保守行事。By default, PLINQ is conservative. 在執行階段,PLINQ 基礎結構會分析查詢的整體結構。At run time, the PLINQ infrastructure analyzes the overall structure of the query. 如果查詢可透過平行化作業來加快執行速度,PLINQ 會將來源序列分割成可同時執行的工作。If the query is likely to yield speedups by parallelization, PLINQ partitions the source sequence into tasks that can be run concurrently. 如果平行處理查詢的方式並不安全,PLINQ 就會循序執行查詢。If it is not safe to parallelize a query, PLINQ just runs the query sequentially. 如果 PLINQ 可選擇是要使用成本可能較高的平行演算法,還是使用成本不高的循序演算法,則依預設它會選擇循序演算法。If PLINQ has a choice between a potentially expensive parallel algorithm or an inexpensive sequential algorithm, it chooses the sequential algorithm by default. 您可以使用 WithExecutionMode 方法和 System.Linq.ParallelExecutionMode 列舉來指示 PLINQ 選取平行演算法。You can use the WithExecutionMode method and the System.Linq.ParallelExecutionMode enumeration to instruct PLINQ to select the parallel algorithm. 當您在測試和測量後得知,某特定查詢在平行執行時速度會更快,便適合這麼做。This is useful when you know by testing and measurement that a particular query executes faster in parallel. 如需詳細資訊,請參閱如何:在 PLINQ 中指定執行模式For more information, see How to: Specify the Execution Mode in PLINQ.

平行處理原則的刻度Degree of Parallelism

根據預設,PLINQ 會使用主機電腦上的所有處理器。By default, PLINQ uses all of the processors on the host computer. 您可以使用 WithDegreeOfParallelism 方法來指示 PLINQ 使用不超過指定數目的處理器。You can instruct PLINQ to use no more than a specified number of processors by using the WithDegreeOfParallelism method. 若您想要確保電腦上執行的其他處理序可獲得一定的 CPU 使用時間,您便可以這麼做。This is useful when you want to make sure that other processes running on the computer receive a certain amount of CPU time. 下列程式碼片段會限制查詢最多只能使用兩個處理器。The following snippet limits the query to utilizing a maximum of two processors.

var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
            where Compute(item) > 42
            select item;
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
            Where Compute(item) > 42
            Select item

當查詢在執行大量非計算繫結工作 (例如檔案 I/O) 的情況下,將平行處理程度指定為大於電腦上的核心數目可能會有幫助。In cases where a query is performing a significant amount of non-compute-bound work such as File I/O, it might be beneficial to specify a degree of parallelism greater than the number of cores on the machine.

比較排序與未排序的平行查詢Ordered Versus Unordered Parallel Queries

在某些查詢中,查詢運算子所產生的結果必須保留來源序列的排序。In some queries, a query operator must produce results that preserve the ordering of the source sequence. 基於此目的,PLINQ 提供 AsOrdered 運算子。PLINQ provides the AsOrdered operator for this purpose. AsOrdered 不同於 AsSequentialAsOrdered is distinct from AsSequential. AsOrdered 序列仍會以平行方式處理,但其結果會新增到緩衝區並加以排序。An AsOrdered sequence is still processed in parallel, but its results are buffered and sorted. 順序保留通常涉及額外工作,因此,AsOrdered 序列的處理速度可能會比 AsUnordered 序列慢。Because order preservation typically involves extra work, an AsOrdered sequence might be processed more slowly than the default AsUnordered sequence. 有許多因素會決定特定的排序平行作業是否會比循序作業更快。Whether a particular ordered parallel operation is faster than a sequential version of the operation depends on many factors.

下列程式碼範例示範如何選擇加入順序保留功能。The following code example shows how to opt in to order preservation.

var evenNums = from num in numbers.AsParallel().AsOrdered()
              where num % 2 == 0
              select num;
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
              Where num Mod 2 = 0
              Select num


如需詳細資訊,請參閱 PLINQ 中的順序保留For more information, see Order Preservation in PLINQ.

比較平行與循序查詢Parallel vs. Sequential Queries

某些作業會要求系統以循序方式傳遞來源資料。Some operations require that the source data be delivered in a sequential manner. ParallelEnumerable 查詢運算子會在必要時自動還原為循序模式。The ParallelEnumerable query operators revert to sequential mode automatically when it is required. 對於使用者定義的查詢運算子和需要循序執行的使用者委派,PLINQ 提供了 AsSequential 方法。For user-defined query operators and user delegates that require sequential execution, PLINQ provides the AsSequential method. 當您使用 AsSequential 時,查詢中的所有後續運算子會循序執行,直到再次呼叫 AsParallel 為止。When you use AsSequential, all subsequent operators in the query are executed sequentially until AsParallel is called again. 如需詳細資訊,請參閱如何:結合平行和循序 LINQ 查詢For more information, see How to: Combine Parallel and Sequential LINQ Queries.

可供合併查詢結果的選項Options for Merging Query Results

當 PLINQ 查詢以平行方式執行時,其來自每個背景工作執行緒的結果,必須合併回主要執行緒以供 foreach 迴圈 (在 Visual Basic 中是 For Each) 取用,或以供插入至清單或陣列。When a PLINQ query executes in parallel, its results from each worker thread must be merged back onto the main thread for consumption by a foreach loop (For Each in Visual Basic), or insertion into a list or array. 在某些情況下,指定特定種類的合併作業可能會有幫助,例如,若您要更快速地開始產生結果的話。In some cases, it might be beneficial to specify a particular kind of merge operation, for example, to begin producing results more quickly. 基於此目的,PLINQ 支援 WithMergeOptions 方法與 ParallelMergeOptions 列舉。For this purpose, PLINQ supports the WithMergeOptions method, and the ParallelMergeOptions enumeration. 如需詳細資訊,請參閱 PLINQ 中的合併選項For more information, see Merge Options in PLINQ.

ForAll 運算子The ForAll Operator

在連續的 LINQ 查詢中,會延後執行,直到查詢是在 foreach (在 Visual Basic 中For Each)迴圈中列舉,或叫用 ToListToArrayToDictionary之類的方法。In sequential LINQ queries, execution is deferred until the query is enumerated either in a foreach (For Each in Visual Basic) loop or by invoking a method such as ToList , ToArray , or ToDictionary. 在 PLINQ 中,您也可以使用 foreach 來執行查詢,並逐一查看各項結果。In PLINQ, you can also use foreach to execute the query and iterate through the results. 不過,foreach 本身並不會以平行方式執行,因此,所有平行工作的輸出必須合併回用來執行迴圈的執行緒。However, foreach itself does not run in parallel, and therefore, it requires that the output from all parallel tasks be merged back into the thread on which the loop is running. 在 PLINQ 中,當您必須保留查詢結果的最終排序時,以及每當您以序列方式處理結果時 (例如,當您為每個元素呼叫 Console.WriteLine 時),您都可以使用 foreachIn PLINQ, you can use foreach when you must preserve the final ordering of the query results, and also whenever you are processing the results in a serial manner, for example when you are calling Console.WriteLine for each element. 在不必保留順序時,以及在結果本身可以平行處理時,若您需要更快速地執行查詢,請使用 ForAll 方法來執行 PLINQ 查詢。For faster query execution when order preservation is not required and when the processing of the results can itself be parallelized, use the ForAll method to execute a PLINQ query. ForAll 不會執此最終合併步驟。ForAll does not perform this final merge step. 下列程式碼範例示範如何使用 ForAll 方法。The following code example shows how to use the ForAll method. 這裡會使用 System.Collections.Concurrent.ConcurrentBag<T>,因為它已經過最佳化,能夠用於同時新增的多個執行緒,而不會嘗試移除任何項目。System.Collections.Concurrent.ConcurrentBag<T> is used here because it is optimized for multiple threads adding concurrently without attempting to remove any items.

var nums = Enumerable.Range(10, 10000);
var query = from num in nums.AsParallel()
            where num % 10 == 0
            select num;

// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
            Where num Mod 10 = 0
            Select num

' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))

下圖顯示 foreachForAll 兩者在執行查詢方面的差異。The following illustration shows the difference between foreach and ForAll with regard to query execution.

ForAll 與 ForEach 的比較ForAll vs. ForEach

取消Cancellation

PLINQ 已與 .NET Framework 4 中的取消作業型別整合。PLINQ is integrated with the cancellation types in .NET Framework 4. (如需詳細資訊,請參閱Managed 執行緒中的取消)。因此,與順序 LINQ to Objects 查詢不同的是,PLINQ 查詢可以取消。(For more information, see Cancellation in Managed Threads.) Therefore, unlike sequential LINQ to Objects queries, PLINQ queries can be canceled. 若要建立可取消的 PLINQ 查詢,請在查詢中使用 WithCancellation 運算子,並提供 CancellationToken 執行個體做為引數。To create a cancelable PLINQ query, use the WithCancellation operator on the query and provide a CancellationToken instance as the argument. 當權杖上的 IsCancellationRequested 屬性設為 true 時,PLINQ 將注意到,並會停止所有執行緒上的處理作業,然後擲回 OperationCanceledExceptionWhen the IsCancellationRequested property on the token is set to true, PLINQ will notice it, stop processing on all threads, and throw an OperationCanceledException.

在設定了取消權杖之後,PLINQ 查詢還是可能會繼續處理某些元素。It is possible that a PLINQ query might continue to process some elements after the cancellation token is set.

若要提高回應速度,您也可以在長時間執行的使用者委派中回應取消要求。For greater responsiveness, you can also respond to cancellation requests in long-running user delegates. 如需詳細資訊,請參閱如何:取消 PLINQ 查詢For more information, see How to: Cancel a PLINQ Query.

例外狀況Exceptions

PLINQ 查詢在執行時,不同的執行緒可能會同時擲回多個例外狀況。When a PLINQ query executes, multiple exceptions might be thrown from different threads simultaneously. 此外,負責處理例外狀況之程式碼所在的執行緒,可能會不同於擲回例外狀況之程式碼所在的執行緒。Also, the code to handle the exception might be on a different thread than the code that threw the exception. PLINQ 會使用 AggregateException 型別將查詢擲回的所有例外狀況封裝起來,然後將這些例外狀況封送處理回呼叫端執行緒。PLINQ uses the AggregateException type to encapsulate all the exceptions that were thrown by a query, and marshal those exceptions back to the calling thread. 呼叫端執行緒只需要一個 try-catch 區塊。On the calling thread, only one try-catch block is required. 不過,您可以逐一查看 AggregateException 中封裝的所有例外狀況,並攔截您可以從中安全復原的任何例外狀況。However, you can iterate through all of the exceptions that are encapsulated in the AggregateException and catch any that you can safely recover from. 在少數情況下,某些例外狀況擲回時可能未包裝於 AggregateException 中,ThreadAbortException 也未包裝。In rare cases, some exceptions may be thrown that are not wrapped in an AggregateException, and ThreadAbortExceptions are also not wrapped.

當系統允許例外狀況反昇至聯結的執行緒時,查詢可能就可以在引發例外狀況之後,繼續處理某些項目。When exceptions are allowed to bubble up back to the joining thread, then it is possible that a query may continue to process some items after the exception is raised.

如需詳細資訊,請參閱如何:處理 PLINQ 查詢中的例外狀況For more information, see How to: Handle Exceptions in a PLINQ Query.

自訂 PartitionerCustom Partitioners

在某些情況下,您可以藉由撰寫自訂 Partitioner 來利用來源資料的某些特性,以改善查詢效能。In some cases, you can improve query performance by writing a custom partitioner that takes advantage of some characteristic of the source data. 在查詢中,自訂 Partitioner 本身就是所查詢的可列舉物件。In the query, the custom partitioner itself is the enumerable object that is queried.

int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(x => SomeFunction(x));
Dim arr(10000) As Integer
Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

PLINQ 支援固定數目的分割 (但在執行階段為了保持負載平衡,系統可能會以動態方式將資料重新指派給這些分割)。PLINQ supports a fixed number of partitions (although data may be dynamically reassigned to those partitions during run time for load balancing.). ForForEach 僅支援動態分割,這表示分割區數目是在執行階段變更的。For and ForEach support only dynamic partitioning, which means that the number of partitions changes at run time. 如需詳細資訊,請參閱 PLINQ 和 TPL 的自訂 PartitionerFor more information, see Custom Partitioners for PLINQ and TPL.

測量 PLINQ 效能Measuring PLINQ Performance

在許多情況下,查詢可平行處理,但設定平行查詢時所帶來的額外負荷,遠超過所獲得的效能好處。In many cases, a query can be parallelized, but the overhead of setting up the parallel query outweighs the performance benefit gained. 如果查詢不會執行許多計算,或如果資料來源很小,PLINQ 查詢的速度可能會比 LINQ to Objects 循序查詢還慢。If a query does not perform much computation or if the data source is small, a PLINQ query may be slower than a sequential LINQ to Objects query. 您可以使用 Visual Studio Team Server 中的 Parallel Performance Analyzer 來比較各種查詢的效能,以找出處理瓶頸,以及判斷您的查詢該平行執行還是循序執行。You can use the Parallel Performance Analyzer in Visual Studio Team Server to compare the performance of various queries, to locate processing bottlenecks, and to determine whether your query is running in parallel or sequentially. 如需詳細資訊,請參閱並行視覺化檢視如何:測量 PLINQ 查詢效能For more information, see Concurrency Visualizer and How to: Measure PLINQ Query Performance.

請參閱See also