Freigeben über


System.Threading.Tasks.TaskScheduler-Klasse

Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.

Die TaskScheduler Klasse stellt einen Aufgabenplaner dar. Durch einen Taskplaner wird sichergestellt, dass die Arbeit einer Aufgabe schließlich ausgeführt wird.

Der Standardmäßige Aufgabenplaner bietet Arbeitsdiebstahl für Lastenausgleich, Threadeinfügung/Deaktivierung für maximalen Durchsatz und insgesamt gute Leistung. Dies ist für die meisten Szenarien in der Regel ausreichend.

Die TaskScheduler Klasse dient auch als Erweiterungspunkt für alle anpassbaren Planungslogiken. Dies umfasst Mechanismen wie das Planen einer Aufgabe für die Ausführung und die Art und Art, wie geplante Vorgänge für Debugger verfügbar gemacht werden sollen. Wenn Sie spezielle Funktionen benötigen, können Sie einen benutzerdefinierten Zeitplan erstellen und für bestimmte Aufgaben oder Abfragen aktivieren.

Der Standardaufgabenplaner und der Threadpool

Der Standardzeitplaner für die Task Parallel Library und PLINQ verwendet den .NET-Threadpool, der durch die ThreadPool Klasse dargestellt wird, um Arbeit in die Warteschlange zu stellen und auszuführen. Der Threadpool verwendet die Informationen, die vom Task Typ bereitgestellt werden, um den differenzierten Parallelismus (kurzlebige Arbeitseinheiten) effizient zu unterstützen, die häufig parallele Aufgaben und Abfragen darstellen.

Die globale Warteschlange im Vergleich zu lokalen Warteschlangen

Der Threadpool Standard eine globale FIFO-Arbeitswarteschlange (first-in, first-out) für Threads in jeder Anwendung enthalten Standard. Wenn ein Programm die ThreadPool.QueueUserWorkItem (oder ThreadPool.UnsafeQueueUserWorkItem) Methode aufruft, wird die Arbeit an dieser freigegebenen Warteschlange platziert und schließlich in den nächsten Thread dewarteschlangen, der verfügbar wird. Ab .NET Framework 4 verwendet diese Warteschlange einen sperrfreien Algorithmus, der der ConcurrentQueue<T> Klasse ähnelt. Durch die Verwendung dieser sperrfreien Implementierung verbringt der Threadpool weniger Zeit, wenn arbeitsbezogene Elemente in die Warteschlange gestellt und nicht in die Warteschlange gestellt werden. Dieser Leistungsvorteil steht allen Programmen zur Verfügung, die den Threadpool verwenden.

Aufgaben der obersten Ebene, bei denen es sich um Aufgaben handelt, die nicht im Kontext einer anderen Aufgabe erstellt werden, werden wie jedes andere Arbeitselement in der globalen Warteschlange abgelegt. Geschachtelte oder untergeordnete Aufgaben, die im Kontext einer anderen Aufgabe erstellt werden, werden hingegen anders behandelt. Ein untergeordnetes Element oder eine geschachtelte Aufgabe wird in einer lokalen Warteschlange abgelegt, die speziell für den Thread vorgesehen ist, in dem die übergeordnete Aufgabe ausgeführt wird. Die übergeordnete Aufgabe ist möglicherweise eine Aufgabe der obersten Ebene. Es kann sich jedoch auch um das untergeordnete Element einer anderen Aufgabe handeln. Wenn dieser Thread für weitere Arbeitsvorgänge bereit ist, wird zuerst eine Suche in der lokalen Warteschlange ausgeführt. Wenn darin Arbeitselemente warten, kann darauf schnell zugegriffen werden. Auf die lokalen Warteschlangen wird in der letzten, first-out-Reihenfolge (First-Out Order, LIFO) zugegriffen, um die Cachelokalität beizubehalten und den Inhalt zu verringern. Weitere Informationen zu untergeordneten Aufgaben und geschachtelten Vorgängen finden Sie unter "Angefügte und getrennte untergeordnete Aufgaben".

Die Verwendung lokaler Warteschlangen verringert nicht nur den Druck auf die globale Warteschlange, sondern nutzt auch die Datenlokalität. Arbeitselemente in der lokalen Warteschlange verweisen häufig auf Datenstrukturen, die sich physisch in der Nähe des Speichers befinden. In diesen Fällen befinden sich die Daten bereits im Cache, nachdem die erste Aufgabe ausgeführt wurde und schnell darauf zugegriffen werden kann. Sowohl Parallel LINQ (PLINQ) als auch die Parallel Klasse verwenden geschachtelte Aufgaben und untergeordnete Aufgaben umfassend und erzielen mithilfe der lokalen Arbeitswarteschlangen erhebliche Geschwindigkeiten.

Arbeit stehlen

Ab .NET Framework 4 verfügt der Threadpool auch über einen Arbeitsdiebstahlalgorithmus, um sicherzustellen, dass keine Threads im Leerlauf sitzen, während andere noch in ihren Warteschlangen arbeiten. Wenn ein Threadpoolthread zusätzliche Arbeit übernehmen kann, wird zuerst am Anfang der lokalen Warteschlange, anschließend in der globalen Warteschlange und zuletzt in den lokalen Warteschlangen anderer Threads gesucht. Wenn ein Arbeitselement in der lokalen Warteschlange eines anderen Threads gefunden wird, wird zunächst Heuristik angewendet, um sicherzustellen, dass die Arbeit effizient ausgeführt werden kann. Wenn dies möglich ist, wird die Arbeitsaufgabe vom Tail (in FIFO-Reihenfolge) in die Warteschlange gestellt. Dadurch werden Konflikte in jeder lokalen Warteschlange verringert und der Datenort beibehalten. Diese Architektur hilft dem Lastenausgleich des Threadpools, effizienter zu arbeiten als bei früheren Versionen.

Lange ausgeführte Aufgaben

Unter Umständen möchten Sie ausdrücklich verhindern, dass eine Aufgabe in einer lokalen Warteschlange abgelegt wird. Sie wissen möglicherweise z. B., dass ein bestimmtes Arbeitselement für eine relativ lange Zeit ausgeführt wird und daher wahrscheinlich alle anderen Arbeitselemente in der lokalen Warteschlange blockiert. In diesem Fall können Sie die System.Threading.Tasks.TaskCreationOptions-Option angeben, die den Planer darauf hinweist, dass u. U. ein weiterer Thread für die Aufgabe erforderlich ist, damit der Fortschritt anderer Threads oder Arbeitselemente in der lokalen Warteschlange nicht blockiert wird. Mit dieser Option vermeiden Sie den Threadpool vollständig, einschließlich der globalen und lokalen Warteschlangen.

Vorgangslining

In einigen Fällen, in denen ein Task Warten ausgeführt wird, kann es synchron auf dem Thread ausgeführt werden, der den Wartevorgang ausführt. Dadurch wird die Leistung verbessert, indem die Notwendigkeit eines zusätzlichen Threads verhindert und stattdessen der vorhandene Thread verwendet wird, der andernfalls blockiert würde. Um Fehler aufgrund einer Neutraabilität zu vermeiden, tritt die Vorgangslining nur auf, wenn das Warteziel in der lokalen Warteschlange des relevanten Threads gefunden wird.

Angeben eines Synchronisierungskontexts

Sie können mit der TaskScheduler.FromCurrentSynchronizationContext-Methode angeben, dass eine Aufgabe für die Ausführung in einem bestimmten Thread geplant werden soll. Dies ist in Frameworks wie Windows Forms und Windows Presentation Foundation hilfreich, wo der Zugriff auf Benutzeroberflächenobjekte oftmals auf Code beschränkt ist, der in dem Thread ausgeführt wird, in dem das Benutzeroberflächenobjekt erstellt wurde.

Im folgenden Beispiel wird die TaskScheduler.FromCurrentSynchronizationContext Methode in einer Windows Presentation Foundation (WPF)-App verwendet, um einen Vorgang im selben Thread zu planen, auf dem das Ui-Steuerelement erstellt wurde. Im Beispiel wird ein Mosaik von Bildern erstellt, die zufällig aus einem angegebenen Verzeichnis ausgewählt werden. Die WPF-Objekte werden zum Laden und Ändern der Größe der Bilder verwendet. Die Unformatierten Pixel werden dann an eine Aufgabe übergeben, die eine For Schleife verwendet, um die Pixeldaten in ein großes Einzelbytearray zu schreiben. Es ist keine Synchronisierung erforderlich, da keine zwei Kacheln dieselben Arrayelemente belegen. Die Kacheln können auch in beliebiger Reihenfolge geschrieben werden, da ihre Position unabhängig von jeder anderen Kachel berechnet wird. Das große Array wird dann an eine Aufgabe übergeben, die im UI-Thread ausgeführt wird, wobei die Pixeldaten in ein Image-Steuerelement geladen werden.

Das Beispiel verschiebt Daten aus dem UI-Thread, ändert sie mithilfe paralleler Schleifen und Task Objekte und übergibt sie dann an eine Aufgabe, die im UI-Thread ausgeführt wird. Dieser Ansatz ist nützlich, wenn Sie die Parallele Aufgabenbibliothek verwenden müssen, um Vorgänge auszuführen, die von der WPF-API nicht unterstützt werden oder nicht ausreichend schnell sind. Eine weitere Möglichkeit zum Erstellen eines Bildmosaiks in WPF besteht darin, ein System.Windows.Controls.WrapPanel Steuerelement zu verwenden und Bilder hinzuzufügen. Die WrapPanel Handhabt die Arbeit der Positionierung der Kacheln. Diese Arbeit kann jedoch nur im UI-Thread ausgeführt werden.

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

Erstellen Sie zum Erstellen des Beispiels ein WPF-Anwendungsprojekt in Visual Studio, und nennen Sie es WPF_CS1 (für ein C#-WPF-Projekt) oder WPF_VB1 (für ein Visual Basic WPF-Projekt). Gehen Sie wie folgt vor:

  1. Ziehen Sie in der Entwurfsansicht ein Image Steuerelement aus der Toolbox in die obere linke Ecke der Entwurfsoberfläche. Nennen Sie im Textfeld "Name" des Eigenschaftenfensters das Steuerelement "Image".

  2. Ziehen Sie ein Button Steuerelement aus der Toolbox in den unteren linken Teil des Anwendungsfensters. Geben Sie in der XAML-Ansicht die Content Eigenschaft der Schaltfläche als "Mosaik erstellen" an, und geben Sie dessen Width Eigenschaft als "100" an. Verbinden das Click Ereignis mit dem button_Click im Code des Beispiels definierten Ereignishandler durch Hinzufügen Click="button_Click" zum <Button> Element. Nennen Sie im Textfeld "Name " des Eigenschaftenfensters das Steuerelement "Schaltfläche".

  3. Ersetzen Sie den gesamten Inhalt der datei MainWindow.xaml.cs oder MainWindow.xaml.vb durch den Code aus diesem Beispiel. Stellen Sie für ein C#-WPF-Projekt sicher, dass der Name des Arbeitsbereichs dem Projektnamen entspricht.

  4. Im Beispiel werden JPEG-Bilder aus einem Verzeichnis mit dem Namen "C:\Users\Public\Pictures\Sample Pictures" gelesen. Erstellen Sie entweder das Verzeichnis, und platzieren Sie einige Bilder darin, oder ändern Sie den Pfad, um auf ein anderes Verzeichnis zu verweisen, das Bilder enthält.

In diesem Beispiel gibt es einige Einschränkungen. Beispielsweise werden nur 32-Bit-Pro-Pixel-Bilder unterstützt; Bilder in anderen Formaten sind während des Größenänderungsvorgangs durch das BitmapImage Objekt beschädigt. Außerdem müssen die Quellbilder größer als die Kachelgröße sein. Als weitere Übung können Sie Funktionen hinzufügen, um mehrere Pixelformate und Dateigrößen zu verarbeiten.