工作排程器
工作排程器是以 System.Threading.Tasks.TaskScheduler 類別表示。 工作排程器可確保最終會執行工作 (Task) 的工作 (Work)。 預設工作排程器是以 .NET Framework 4 ThreadPool 為基礎,其可提供工作竊取 (以達到平衡負載)、執行緒插入/停用 (以達到最大輸送量),以及整體良好效能。 這應該足以應付大多數的情況。 但是,如果您需要特殊功能,可以針對特定工作或查詢建立自訂排程器,然後加以啟用。 如需如何建立和使用自訂工作排程器的詳細資訊,請參閱 HOW TO:建立會限制並行程度的工作排程器。 如需自訂排程器的其他範例,請參閱 MSDN Code Gallery 網站上的平行擴充功能範例 (英文)。
預設工作排程器和 ThreadPool
工作平行程式庫和 PLINQ 的預設排程器會使用 .NET Framework ThreadPool 將工作排入佇列並加以執行。 在 .NET Framework 4 中,ThreadPool 會使用 System.Threading.Tasks.Task 型別所提供的資訊,有效地支援平行工作和查詢通常所代表的細部平行處理原則 (存留時間短暫的工作單位)。
ThreadPool 全域佇列和本機佇列的比較
在舊版的 .NET Framework 中,ThreadPool 會針對每個應用程式定義域中的執行緒,維護全域 FIFO (先進先出) 工作佇列。 每當有程式呼叫 QueueUserWorkItem (或 UnsafeQueueUserWorkItem) 時,工作便會進入這個共用佇列,最後再從佇列中移出,以進到下一個可用的執行緒執行。 在 .NET Framework 4 中,這個佇列經過改良,可以使用類似 ConcurrentQueue 類別的無鎖定演算法。使用這種無鎖定實作方式時,ThreadPool 花在將工作項目放入或移出佇列的時間比較少。 所有使用 ThreadPool 的程式都可獲得這樣的效能改善。
如同任何其他的工作 (Work) 項目,最上層工作 (Task) (也就是不是在其他工作 (Task) 的內容中建立的工作 (Task)) 會放入全域佇列中。 但是,巢狀工作或子工作 (也就是在其他工作的內容中建立的工作) 則會受到相當不同的處理。 子工作或巢狀工作會放入執行父工作的執行緒專屬的本機佇列中。 父工作可以是最上層工作,也可以是其他工作的子系。 這個執行緒在準備好要處理更多工作時,會先查看本機佇列。 如果本機佇列中有等待處理的工作項目,就可以快速存取這些工作項目。 本機佇列係採後進先出順序 (LIFO) 的存取方式,以便保留快取位置並減少爭用情形。 如需子工作和巢狀工作的詳細資訊,請參閱巢狀工作和子工作。
下列範例顯示一些排定在全域佇列中的工作,以及一些排定在本機佇列上的工作。
Sub QueueTasks()
' TaskA is a top level task.
Dim taskA = Task.Factory.StartNew(Sub()
Console.WriteLine("I was enqueued on the thread pool's global queue.")
' TaskB is a nested task and TaskC is a child task. Both go to local queue.
Dim taskB = New Task(Sub() Console.WriteLine("I was enqueued on the local queue."))
Dim taskC = New Task(Sub() Console.WriteLine("I was enqueued on the local queue, too."),
TaskCreationOptions.AttachedToParent)
taskB.Start()
taskC.Start()
End Sub)
End Sub
void QueueTasks()
{
// TaskA is a top level task.
Task taskA = Task.Factory.StartNew( () =>
{
Console.WriteLine("I was enqueued on the thread pool's global queue.");
// TaskB is a nested task and TaskC is a child task. Both go to local queue.
Task taskB = new Task( ()=> Console.WriteLine("I was enqueued on the local queue."));
Task taskC = new Task(() => Console.WriteLine("I was enqueued on the local queue, too."),
TaskCreationOptions.AttachedToParent);
taskB.Start();
taskC.Start();
});
}
使用本機佇列不僅能減輕全域佇列的壓力,還可以善用資料位置。 本機佇列中的工作項目經常會參考記憶體中實際相鄰的資料結構。 在這些情況下,在執行第一項工作之後,資料便已經在快取中,而可供人快速存取。 平行 LINQ (PLINQ) 和 Parallel 類別都會大量使用巢狀工作和子工作,並且利用本機工作佇列達到顯著的加速效果。
工作竊取
.NET Framework 4 ThreadPool 還提供了工作竊取演算法,協助確保不會有某些執行緒處於閒置狀態,而某些執行緒仍有工作在佇列中的情形。 執行緒集區的執行緒在準備好要處理更多工作時,會先查看自己本機佇列的開頭,接著查看全域佇列,然後再查看其他執行緒的本機佇列。 如果在其他執行緒的本機佇列中發現了工作項目,它會先套用啟發式,確定自己能夠有效率地執行工作。 如果可以,它就會取出在佇列尾端的工作項目 (以 FIFO 順序)。 這麼做可以減少每個本機佇列上的爭用情形,並保留資料位置。這個架構可協助 .NET Framework 4 ThreadPool 以比舊版更有效率的方式來平衡工作負載。
長時間執行的工作
您可以明確防止將工作放入本機佇列中。 例如,您知道某個工作項目會執行相當長的一段時間,而可能會阻礙本機佇列上所有其他工作項目的進行。 在這種情況下,您可以指定 LongRunning 選項,這個選項可讓排程器心裡有個底,知道可能需要再加一個執行緒來執行工作,才不會阻礙本機佇列上其他執行緒或工作項目的進度。 使用這個選項,您可以完全避開 ThreadPool (包括全域和本機佇列)。
工作內嵌
有時候,其他工作會等候某個工作完成,這時就可以在執行等候作業的執行緒上,以同步方式執行等候完成的工作。 這樣做可以提高效能,因為如此就不需再加一個執行緒,而是直接利用等候的現有執行緒來執行,而不會阻礙工作進度。 為了預防之後回到錯誤的執行緒,工作內嵌只有在相關執行緒的本機佇列中出現等候目標時才會執行。
指定同步處理內容
您可以使用 TaskScheduler.FromCurrentSynchronizationContext 方法,指定應該將工作排定在特定執行緒上執行。 在 Windows Form 和 Windows Presentation Foundation 等架構中,只有在當初建立使用者介面物件的執行緒上執行的程式碼能夠存取該 UI 物件,因此很適合使用這個方法。 如需詳細資訊,請參閱 HOW TO:排程在特定的同步處理內容上運作。