System.Threading.Tasks.TaskScheduler クラス

この記事では、この API のリファレンス ドキュメントへの補足的な解説を提供します。

このクラスは TaskScheduler タスク スケジューラを表します。 タスク スケジューラは、タスクの作業が最終的に実行されるようにします。

既定のタスク スケジューラは、負荷分散、最大スループットのスレッド挿入/廃止、全体的な良好なパフォーマンスのための作業盗み取りを提供します。 ほとんどのシナリオでは、既定のタスク スケジューラで十分です。

このクラスは TaskScheduler 、カスタマイズ可能なすべてのスケジューリング ロジックの拡張ポイントとしても機能します。 これには、実行するタスクをスケジュールする方法や、スケジュールされたタスクをデバッガーに公開する方法などのメカニズムが含まれます。 特別な機能が必要な場合は、カスタム スケジューラを作成し、特定のタスクまたはクエリに対して有効にすることができます。

既定のタスク スケジューラとスレッド プール

タスク並列ライブラリと PLINQ の既定のスケジューラは、クラスによって表される .NET スレッド プールを ThreadPool 使用して、作業をキューに格納して実行します。 スレッド プールは、型によって提供される情報を Task 使用して、並列タスクとクエリが頻繁に表すきめ細かい並列処理 (有効期間の短い作業単位) を効率的にサポートします。

グローバル キューとローカル キュー

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

トップレベル タスクは、別のタスクのコンテキストで作成されないタスクのことで、他の作業項目と同様にグローバル キューに配置されます。 ただし、別のタスクのコンテキストで作成される入れ子のタスクまたは子タスクは、まったく異なる方法で処理されます。 子タスクまたは入れ子のタスクは、親タスクが実行されているスレッドに固有のローカル キューに配置されます。 親タスクはトップレベルのタスクである場合もあれば、別のタスクの子である場合もあります。 このスレッドは、追加の作業を処理する準備が整ったら、最初にローカル キューを検索します。 作業項目がローカル キューで待機している場合は、それらにすばやくアクセスできます。 ローカル キューには、キャッシュのローカリティを維持し、競合を減らすために、先入れ先出し順 (LIFO) でアクセスされます。 子タスクと入れ子になったタスクの詳細については、「アタッチされた子タスクとデタッチされた子タスク」を参照してください

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

作業の盗み

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

実行時間の長いタスク

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

タスクのインライン化

待機中の場合、待機操作を Task 実行しているスレッドで同期的に実行される場合があります。 これにより、追加のスレッドの必要性を防ぎ、代わりに既存のスレッドを使用することでパフォーマンスが向上します。これにより、それ以外の場合はブロックされます。 再入によるエラーを防ぐために、タスクのインライン化は、関連するスレッドのローカル キューで待機ターゲットが見つかった場合にのみ発生します。

同期コンテキストを指定する

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

次の例では、Windows Presentation Foundation (WPF) アプリのメソッドを使用 TaskScheduler.FromCurrentSynchronizationContext して、ユーザー インターフェイス (UI) コントロールが作成されたのと同じスレッドでタスクをスケジュールします。 この例では、指定したディレクトリからランダムに選択された画像のモザイクを作成します。 WPF オブジェクトは、イメージの読み込みとサイズ変更に使用されます。 その後、生ピクセルは、ループを使用 For してピクセル データを大きな 1 バイト配列に書き込むタスクに渡されます。 2 つのタイルが同じ配列要素を占有しないため、同期は必要ありません。 また、タイルの位置は他のタイルとは別に計算されるため、任意の順序で書き込むこともできます。 その後、大きな配列が UI スレッドで実行されるタスクに渡され、ピクセル データが Image コントロールに読み込まれます。

この例では、データを UI スレッドから移動し、並列ループと Task オブジェクトを使用して変更した後、UI スレッドで実行されるタスクに戻します。 この方法は、タスク並列ライブラリを使用して、WPF API でサポートされていない操作、または十分に高速ではない操作を実行する必要がある場合に便利です。 WPF で画像モザイクを作成するもう 1 つの方法は、コントロールを System.Windows.Controls.WrapPanel 使用して画像を追加することです。 タイル WrapPanel の配置作業を処理します。 ただし、この作業は UI スレッドでのみ実行できます。

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WPF_CS1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int fileCount;
        int colCount;
        int rowCount;
        private int tilePixelHeight;
        private int tilePixelWidth;
        private int largeImagePixelHeight;
        private int largeImagePixelWidth;
        private int largeImageStride;
        PixelFormat format;
        BitmapPalette palette = null;

        public MainWindow()
        {
            InitializeComponent();

            // For this example, values are hard-coded to a mosaic of 8x8 tiles.
            // Each tile is 50 pixels high and 66 pixels wide and 32 bits per pixel.
            colCount = 12;
            rowCount = 8;
            tilePixelHeight = 50;
            tilePixelWidth = 66;
            largeImagePixelHeight = tilePixelHeight * rowCount;
            largeImagePixelWidth = tilePixelWidth * colCount;
            largeImageStride = largeImagePixelWidth * (32 / 8);
            this.Width = largeImagePixelWidth + 40;
            image.Width = largeImagePixelWidth;
            image.Height = largeImagePixelHeight;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {

            // For best results use 1024 x 768 jpg files at 32bpp.
            string[] files = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures\", "*.jpg");

            fileCount = files.Length;
            Task<byte[]>[] images = new Task<byte[]>[fileCount];
            for (int i = 0; i < fileCount; i++)
            {
                int x = i;
                images[x] = Task.Factory.StartNew(() => LoadImage(files[x]));
            }

            // When they've all been loaded, tile them into a single byte array.
            var tiledImage = Task.Factory.ContinueWhenAll(
                images, (i) => TileImages(i));

            // We are currently on the UI thread. Save the sync context and pass it to
            // the next task so that it can access the UI control "image".
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            // On the UI thread, put the bytes into a bitmap and
            // display it in the Image control.
            var t3 = tiledImage.ContinueWith((antecedent) =>
            {
                // Get System DPI.
                Matrix m = PresentationSource.FromVisual(Application.Current.MainWindow)
                                            .CompositionTarget.TransformToDevice;
                double dpiX = m.M11;
                double dpiY = m.M22;

                BitmapSource bms = BitmapSource.Create(largeImagePixelWidth,
                    largeImagePixelHeight,
                    dpiX,
                    dpiY,
                    format,
                    palette, //use default palette
                    antecedent.Result,
                    largeImageStride);
                image.Source = bms;
            }, UISyncContext);
        }

        byte[] LoadImage(string filename)
        {
            // Use the WPF BitmapImage class to load and
            // resize the bitmap. NOTE: Only 32bpp formats are supported correctly.
            // Support for additional color formats is left as an exercise
            // for the reader. For more information, see documentation for ColorConvertedBitmap.

            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.UriSource = new Uri(filename);
            bitmapImage.DecodePixelHeight = tilePixelHeight;
            bitmapImage.DecodePixelWidth = tilePixelWidth;
            bitmapImage.EndInit();

            format = bitmapImage.Format;
            int size = (int)(bitmapImage.Height * bitmapImage.Width);
            int stride = (int)bitmapImage.Width * 4;
            byte[] dest = new byte[stride * tilePixelHeight];

            bitmapImage.CopyPixels(dest, stride, 0);

            return dest;
        }

        int Stride(int pixelWidth, int bitsPerPixel)
        {
            return (((pixelWidth * bitsPerPixel + 31) / 32) * 4);
        }

        // Map the individual image tiles to the large image
        // in parallel. Any kind of raw image manipulation can be
        // done here because we are not attempting to access any
        // WPF controls from multiple threads.
        byte[] TileImages(Task<byte[]>[] sourceImages)
        {
            byte[] largeImage = new byte[largeImagePixelHeight * largeImageStride];
            int tileImageStride = tilePixelWidth * 4; // hard coded to 32bpp

            Random rand = new Random();
            Parallel.For(0, rowCount * colCount, (i) =>
            {
                // Pick one of the images at random for this tile.
                int cur = rand.Next(0, sourceImages.Length);
                byte[] pixels = sourceImages[cur].Result;

                // Get the starting index for this tile.
                int row = i / colCount;
                int col = (int)(i % colCount);
                int idx = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride));

                // Write the pixels for the current tile. The pixels are not contiguous
                // in the array, therefore we have to advance the index by the image stride
                // (minus the stride of the tile) for each scanline of the tile.
                int tileImageIndex = 0;
                for (int j = 0; j < tilePixelHeight; j++)
                {
                    // Write the next scanline for this tile.
                    for (int k = 0; k < tileImageStride; k++)
                    {
                        largeImage[idx++] = pixels[tileImageIndex++];
                    }
                    // Advance to the beginning of the next scanline.
                    idx += largeImageStride - tileImageStride;
                }
            });
            return largeImage;
        }
    }
}

Partial Public Class MainWindow : Inherits Window
    Dim fileCount As Integer
    Dim colCount As Integer
    Dim rowCount As Integer
    Dim tilePixelHeight As Integer
    Dim tilePixelWidth As Integer
    Dim largeImagePixelHeight As Integer
    Dim largeImagePixelWidth As Integer
    Dim largeImageStride As Integer
    Dim format As PixelFormat
    Dim palette As BitmapPalette = Nothing

    Public Sub New()
        InitializeComponent()

        ' For this example, values are hard-coded to a mosaic of 8x8 tiles.
        ' Each tile Is 50 pixels high and 66 pixels wide and 32 bits per pixel.
        colCount = 12
        rowCount = 8
        tilePixelHeight = 50
        tilePixelWidth = 66
        largeImagePixelHeight = tilePixelHeight * rowCount
        largeImagePixelWidth = tilePixelWidth * colCount
        largeImageStride = largeImagePixelWidth * (32 / 8)
        Me.Width = largeImagePixelWidth + 40
        image.Width = largeImagePixelWidth
        image.Height = largeImagePixelHeight
    End Sub

    Private Sub button_Click(sender As Object, e As RoutedEventArgs) _
        Handles button.Click

        ' For best results use 1024 x 768 jpg files at 32bpp.
        Dim files() As String = System.IO.Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures\", "*.jpg")

        fileCount = files.Length
        Dim images(fileCount - 1) As Task(Of Byte())
        For i As Integer = 0 To fileCount - 1
            Dim x As Integer = i
            images(x) = Task.Factory.StartNew(Function() LoadImage(files(x)))
        Next

        ' When they have all been loaded, tile them into a single byte array.
        'var tiledImage = Task.Factory.ContinueWhenAll(
        '        images, (i) >= TileImages(i));

        '        Dim tiledImage As Task(Of Byte()) = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())) TileImages(i))
        Dim tiledImage = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())()) TileImages(i))
        ' We are currently on the UI thread. Save the sync context and pass it to
        ' the next task so that it can access the UI control "image1".
        Dim UISyncContext = TaskScheduler.FromCurrentSynchronizationContext()

        ' On the UI thread, put the bytes into a bitmap and
        ' display it in the Image control.
        Dim t3 = tiledImage.ContinueWith(Sub(antecedent)
                                             ' Get System DPI.
                                             Dim m As Matrix = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice
                                             Dim dpiX As Double = m.M11
                                             Dim dpiY As Double = m.M22

                                             ' Use the default palette in creating the bitmap.
                                             Dim bms As BitmapSource = BitmapSource.Create(largeImagePixelWidth,
                                                                                           largeImagePixelHeight,
                                             dpiX,
                                             dpiY,
                                             format,
                                             palette,
                                             antecedent.Result,
                                             largeImageStride)
                                             image.Source = bms
                                         End Sub, UISyncContext)
    End Sub

    Public Function LoadImage(filename As String) As Byte()
        ' Use the WPF BitmapImage class to load and 
        ' resize the bitmap. NOTE: Only 32bpp formats are supported correctly.
        ' Support for additional color formats Is left as an exercise
        ' for the reader. For more information, see documentation for ColorConvertedBitmap.
        Dim bitmapImage As New BitmapImage()
        bitmapImage.BeginInit()
        bitmapImage.UriSource = New Uri(filename)
        bitmapImage.DecodePixelHeight = tilePixelHeight
        bitmapImage.DecodePixelWidth = tilePixelWidth
        bitmapImage.EndInit()

        format = bitmapImage.Format
        Dim size As Integer = CInt(bitmapImage.Height * bitmapImage.Width)
        Dim stride As Integer = CInt(bitmapImage.Width * 4)
        Dim dest(stride * tilePixelHeight - 1) As Byte

        bitmapImage.CopyPixels(dest, stride, 0)

        Return dest
    End Function

    Function Stride(pixelWidth As Integer, bitsPerPixel As Integer) As Integer
        Return (((pixelWidth * bitsPerPixel + 31) / 32) * 4)
    End Function

    ' Map the individual image tiles to the large image
    ' in parallel. Any kind of raw image manipulation can be
    ' done here because we are Not attempting to access any 
    ' WPF controls from multiple threads.
    Function TileImages(sourceImages As Task(Of Byte())()) As Byte()
        Dim largeImage(largeImagePixelHeight * largeImageStride - 1) As Byte
        Dim tileImageStride As Integer = tilePixelWidth * 4 ' hard coded To 32bpp

        Dim rand As New Random()
        Parallel.For(0, rowCount * colCount, Sub(i)
                                                 ' Pick one of the images at random for this tile.
                                                 Dim cur As Integer = rand.Next(0, sourceImages.Length)
                                                 Dim pixels() As Byte = sourceImages(cur).Result

                                                 ' Get the starting index for this tile.
                                                 Dim row As Integer = i \ colCount
                                                 Dim col As Integer = i Mod colCount
                                                 Dim idx As Integer = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride))

                                                 ' Write the pixels for the current tile. The pixels are Not contiguous
                                                 ' in the array, therefore we have to advance the index by the image stride
                                                 ' (minus the stride of the tile) for each scanline of the tile.
                                                 Dim tileImageIndex As Integer = 0
                                                 For j As Integer = 0 To tilePixelHeight - 1
                                                     ' Write the next scanline for this tile.
                                                     For k As Integer = 0 To tileImageStride - 1
                                                         largeImage(idx) = pixels(tileImageIndex)
                                                         idx += 1
                                                         tileImageIndex += 1
                                                     Next
                                                     ' Advance to the beginning of the next scanline.
                                                     idx += largeImageStride - tileImageStride
                                                 Next
                                             End Sub)
        Return largeImage
    End Function
End Class

この例を作成するには、Visual Studio で WPF アプリケーション プロジェクトを作成し、WPF_CS1 (C# WPF プロジェクトの場合) またはWPF_VB1 (Visual Basic WPF プロジェクトの場合) に名前を付けます。 次に、次を実行します。

  1. デザイン ビューで、ツールボックスからデザイン サーフェイスの左上隅にコントロールをドラッグImageします。 [プロパティ] ウィンドウの [名前] ボックスに、コントロールに "image" という名前を付けます。

  2. Buttonツールボックスからアプリケーション ウィンドウの左下部分にコントロールをドラッグします。 XAML ビューで、ボタンのプロパティを Content "モザイクにする" として指定し、その Width プロパティを "100" として指定します。 要素にClick追加Click="button_Click"して、button_Click例のコードで定義されているイベント ハンドラーでイベントを<Button>接続します。 [プロパティ] ウィンドウの [名前] ボックスに、コントロールに "button" という名前を付けます。

  3. MainWindow.xaml.csまたはMainWindow.xaml.vb ファイルの内容全体を、この例のコードに置き換えます。 C# WPF プロジェクトの場合は、ワークスペースの名前がプロジェクト名と一致していることを確認します。

  4. この例では、C:\Users\Public\Pictures\Sample Pictures という名前 のディレクトリから JPEG 画像を読み取ります。 ディレクトリを作成してイメージを配置するか、パスを変更してイメージを含む他のディレクトリを参照します。

この例にはいくつかの制限があります。 たとえば、32 ビット/ピクセルのイメージのみがサポートされます。他の形式の画像は、サイズ変更操作中に BitmapImage オブジェクトによって破損します。 また、ソース イメージはすべてタイル サイズより大きくする必要があります。 さらに演習として、複数のピクセル形式とファイル サイズを処理する機能を追加できます。