タスク スケジューラ

タスク スケジューラは、System.Threading.Tasks.TaskScheduler クラスによって表されます。 タスク スケジューラは、タスクの作業が最終的に実行されるようにします。 既定のタスク スケジューラは、負荷分散、スループット最大化のためのスレッドのインジェクション/リタイヤ、および全体のパフォーマンスの向上のためのワーク スティーリングを提供する .NET Framework 4 ThreadPool に基づいています。 ほとんどのシナリオでは、既定のタスク スケジューラで十分です。 ただし、特別な機能が必要な場合は、カスタム スケジューラを作成し、特定のタスクまたはクエリに対してそのカスタム スケジューラを有効にすることができます。 カスタム タスク スケジューラの作成方法および使用方法の詳細については、「方法: 同時実行の程度を制限するタスク スケジューラを作成する」を参照してください。 カスタム スケジューラのその他の例については、MSDN コード ギャラリー Web サイトの並列拡張機能の例を参照してください。

既定のタスク スケジューラと ThreadPool

タスク並列ライブラリと PLINQ の既定のスケジューラは、作業のキューへの配置および実行に .NET Framework ThreadPool を使用します。 .NET Framework 4 の ThreadPool は、System.Threading.Tasks.Task 型によって提供される情報を使用して、並列タスクや並列クエリによって表されることが多い、粒度の高い並列化 (有効期間が短い作業単位) を効率的にサポートします。

ThreadPool のグローバル キューとローカル キューの比較

旧バージョンの .NET Framework では、各アプリケーション ドメインのスレッドのグローバル FIFO (先入れ先出し) 作業キューが ThreadPool に保持されます。 プログラムが QueueUserWorkItem (または UnsafeQueueUserWorkItem) を呼び出すたびに、作業がこの共有キューに配置されます。作業は、最終的にこのキューから取り出され、使用可能になった次のスレッドに配置されます。 .NET Framework 4 では、このキューは ConcurrentQueue クラスに似たロック制御不要のアルゴリズムを使用するように改良されました。ThreadPool では、このロック制御不要の実装を使用することで、作業項目をキューに置いたりキューから削除したりするときに要する時間を短縮しています。 このパフォーマンス上の利点は、ThreadPool を使用するすべてのプログラムで得られます。

トップレベル タスクは、別のタスクのコンテキストで作成されないタスクのことで、他の作業項目と同様にグローバル キューに配置されます。 ただし、別のタスクのコンテキストで作成される入れ子のタスクまたは子タスクは、まったく異なる方法で処理されます。 子タスクまたは入れ子のタスクは、親タスクが実行されているスレッドに固有のローカル キューに配置されます。 親タスクはトップレベルのタスクである場合もあれば、別のタスクの子である場合もあります。 このスレッドは、追加の作業を処理する準備が整ったら、最初にローカル キューを検索します。 作業項目がローカル キューで待機している場合は、それらにすばやくアクセスできます。 ローカル キューへのアクセスは、後入れ先出し (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();

    });
}

ローカル キューを使用すると、グローバル キューへの負荷が減るだけでなく、データの局所性も活用できます。 ローカル キュー内の作業項目は、メモリ内にある物理的に似たデータ構造を頻繁に参照します。 このような場合、最初のタスクの実行後にデータが既にキャッシュ内に存在するため、すばやくデータにアクセスできます。 入れ子のタスクと子タスクを頻繁に使用する Parallel LINQ (PLINQ) および Parallel クラスでは、ローカル作業キューを使用することで大幅な高速化を実現しています。

ワーク スティーリング

.NET Framework 4 ThreadPool は、ワーク スティーリング アルゴリズムも備えています。ワーク スティーリング アルゴリズムは、あるスレッドのキューに作業が配置されているときに別のスレッドがアイドル状態になるのを防ぐのに役立ちます。 スレッド プールのスレッドは、追加の作業を処理する準備が整ったら、最初にローカル キューの先頭を探します。次にグローバル キューを探し、最後に他のスレッドのローカル キューを探します。 別のスレッドのローカル キューで作業項目が見つかった場合、作業を効率的に実行できるように、最初にヒューリスティックを適用します。 ヒューリスティックを適用できる場合は、キューの後部から作業項目を取り出します (FIFO の順序)。 これにより、各ローカル キューでの競合が減り、データの局所性が保持されます。このアーキテクチャにより、.NET Framework 4 の ThreadPool は、旧バージョンよりも効率的に負荷を分散できます。

長時間実行されるタスク

タスクがローカル キューに配置されるのを明示的に防止したい場合があります。 たとえば、特定の作業項目がかなり長い時間実行され、ローカル キューの他の作業項目をすべてブロックする可能性があることがわかっている場合などです。 このような場合は、LongRunning オプションを指定できます。このオプションは、タスクの処理に追加のスレッドが必要になる可能性があるというヒントをスケジューラに提供し、他のスレッドまたはローカル キューの作業項目の進行をスケジューラがブロックするのを防ぎます。 このオプションを使用すると、ThreadPool がグローバル キューやローカル キューを含めて完全に防止されます。

タスクのインライン展開

場合によっては、タスクが待機しているときに、待機操作を実行しているスレッドでタスクが同期的に実行されることがあります。 これによりパフォーマンスが向上します。これは、この処理が行われなければブロックされていた既存のスレッドを活用することで、追加のスレッドが不要になるからです。 タスクのインライン展開は、関連するスレッドのローカル キューで待機対象が見つかった場合にのみ行われます。これは再入によるエラーを防ぐためです。

同期コンテキストの指定

TaskScheduler.FromCurrentSynchronizationContext メソッドを使用すると、タスクが特定のスレッドで実行されるようにスケジュールできます。 これは、Windows フォームや Windows Presentation Foundation などのフレームワークで役立ちます。これらのフレームワークでは、多くの場合、ユーザー インターフェイス オブジェクトへのアクセスが、その UI オブジェクトが作成されたスレッドで実行されているコードに制限されるからです。 詳細については、「方法: 指定された同期コンテキストで作業をスケジュールする」を参照してください。

参照

参照

TaskScheduler

概念

タスク並列ライブラリ

その他の技術情報

方法: 同時実行の程度を制限するタスク スケジューラを作成する

方法: 指定された同期コンテキストで作業をスケジュールする

タスク ファクトリ