System.Threading.Tasks.TaskScheduler-klass

Anmärkning

Den här artikeln innehåller ytterligare kommentarer till referensdokumentationen för det här API:et.

Klassen TaskScheduler representerar en uppgiftsschemaläggare. En schemaläggare ser till att en uppgifts arbete slutligen utförs.

Standarduppgiftsschemaläggaren tillhandahåller arbetsstöld för belastningsutjämning, trådinförsel/avveckling för maximal genomströmning och överlag god prestanda. Det bör vara tillräckligt för de flesta scenarier.

Klassen TaskScheduler fungerar också som tilläggspunkt för all anpassningsbar schemaläggningslogik. Detta omfattar mekanismer som hur du schemalägger en uppgift för att köras och hur schemalagda uppgifter ska göras tillgängliga för felsökningsprogram. Om du behöver särskilda funktioner kan du skapa en anpassad schemaläggare och aktivera den för specifika uppgifter eller frågor.

Standardaktivitetsschemaläggaren och trådpoolen

Standardschemaläggaren för det parallella aktivitetsbiblioteket och PLINQ använder .NET-trådpoolen ThreadPool , som representeras av klassen, för att köa och köra arbete. Trådpoolen använder den information som tillhandahålls av Task typen för att effektivt stödja den detaljerade parallellitet (kortvariga arbetsenheter) som parallella uppgifter och frågor ofta representerar.

Den globala kön jämfört med lokala köer

Trådpoolen har en global FIFO-arbetskö (först in och först ut) för trådar i varje programdomän. Varje gång ett program anropar metoden ThreadPool.QueueUserWorkItem (eller ThreadPool.UnsafeQueueUserWorkItem) placeras arbetet i den gemensamma kön och flyttas därefter till nästa tråd som blir tillgänglig. Den här kön använder från och med .NET Framework 4 en låsfri algoritm som liknar klassen ConcurrentQueue<T>. Genom att använda den här låsfria implementeringen ägnar trådpoolen mindre tid åt att köa och avköna arbetsobjekt. Den här prestandaförmånen är tillgänglig för alla program som använder trådpoolen.

Uppgifter på den översta nivån, som är uppgifter som inte skapas i kontexten för en annan uppgift, placeras i den globala kön precis som andra arbetsobjekt. Uppgifter som är kapslade eller underordnade, och som skapas inom ramen för en annan uppgift, hanteras dock på ett helt annat sätt. En underordnad eller inbäddad uppgift placeras i en lokal kö som är specifik för den tråd som den överordnade uppgiften körs på. Den överordnade uppgiften kan vara en uppgift på den översta nivån eller också kan den vara barn till en annan uppgift. När den här tråden är redo för mer arbete tittar den först i den lokala kön. Om arbetsobjekt väntar där kan de nås snabbt. De lokala köerna nås i LIFO-ordning (last-in, first-out) för att bevara cachelokalitet och minska kontentionen. Mer information om underordnade uppgifter och kapslade aktiviteter finns i Bifogade och kopplade underordnade aktiviteter.

Användningen av lokala köer minskar inte bara trycket på den globala kön, utan drar också nytta av datalokaliteten. Arbetsobjekt i den lokala kön refererar ofta till datastrukturer som ligger fysiskt nära varandra i minnet. I dessa fall finns data redan i cacheminnet när den första aktiviteten har körts och kan nås snabbt. Både Parallell LINQ (PLINQ) och Parallel klassen använder kapslade uppgifter och underuppgifter i stor utsträckning, och uppnår betydande hastighetsökningar med hjälp av lokala arbetsköer.

Arbetsstöld

Från och med .NET Framework 4 har trådpoolen också en arbetsstöldsalgoritm för att säkerställa att inga trådar är inaktiva medan andra fortfarande har arbete i sina köer. När en trådpoolstråd är redo för mer arbete tittar den först på början av sin lokala kö, sedan i den globala kön och sedan i de lokala köerna för andra trådar. Om den hittar ett arbetsobjekt i den lokala kö i en annan tråd, tillämpar den först heuristiker för att se till att den kan utföra arbetet effektivt. Om det kan, avköar det arbetsobjektet från svansen (i FIFO-ordning). Detta minskar konkurrensen i varje lokal kö och bevarar datalokaliteten. Den här arkitekturen hjälper trådpoolens belastningsutjämning att fungera effektivare än tidigare versioner.

Tidskrävande uppgifter

Du kanske uttryckligen vill förhindra att en uppgift placeras i en lokal kö. Du kanske till exempel vet att ett visst arbetsobjekt kommer att köras relativt länge och sannolikt kommer att blockera alla andra arbetsobjekt i den lokala kön. I det här fallet kan du ange System.Threading.Tasks.TaskCreationOptions alternativet, som ger en ledtråd till schemaläggaren om att ytterligare en tråd kan krävas för aktiviteten så att den inte blockerar framåtförloppet för andra trådar eller arbetsobjekt i den lokala kön. Med det här alternativet undviker du trådpoolen helt, inklusive de globala och lokala köerna.

Inlining av uppgift

I vissa fall när en Task väntas på kan den utföras synkront på tråden som utför vänteåtgärden. Detta förbättrar prestandan genom att förhindra behovet av ytterligare en tråd och i stället använda den befintliga tråden, som annars skulle ha blockerats. För att förhindra fel på grund av återinträde sker uppgiftsinlinering endast när väntemålet hittas i den relevanta trådens lokala kö.

Ange en synkroniseringskontext

Du kan använda TaskScheduler.FromCurrentSynchronizationContext metoden för att ange att en aktivitet ska schemaläggas att köras på en viss tråd. Detta är användbart i ramverk som Windows Forms och Windows Presentation Foundation där åtkomsten till användargränssnittsobjekt ofta begränsas till kod som körs på samma tråd som användargränssnittsobjektet skapades på.

I följande exempel används TaskScheduler.FromCurrentSynchronizationContext metoden i en WPF-app (Windows Presentation Foundation) för att schemalägga en uppgift på samma tråd som användargränssnittskontrollen skapades på. Exemplet skapar en mosaik av bilder som väljs slumpmässigt från en angiven katalog. WPF-objekten används för att läsa in och ändra storlek på bilderna. De råa pixlarna skickas sedan till en uppgift som använder en For-loop för att skriva pixeldata till ett stort enkelbytesfält. Ingen synkronisering krävs eftersom inga två paneler upptar samma matriselement. Panelerna kan också skrivas i valfri ordning eftersom deras position beräknas oberoende av andra paneler. Den stora matrisen skickas sedan till en uppgift som körs på användargränssnittstråden, där pixeldata läses in i en bildkontroll.

Exemplet flyttar data från användargränssnittstråden, ändrar dem med hjälp av parallella loopar och Task objekt och skickar dem sedan tillbaka till en uppgift som körs i användargränssnittstråden. Den här metoden är användbar när du måste använda det parallella aktivitetsbiblioteket för att utföra åtgärder som antingen inte stöds av WPF-API:et eller inte är tillräckligt snabba. Ett annat sätt att skapa en bildmosaik i WPF är att använda en System.Windows.Controls.WrapPanel kontroll och lägga till bilder i den. Det WrapPanel hanterar arbetet med att placera plattorna. Det här arbetet kan dock bara utföras på användargränssnittstråden.

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

Skapa exemplet genom att skapa ett WPF-programprojekt i Visual Studio och ge det namnet WPF_CS1 (för ett C#WPF-projekt) eller WPF_VB1 (för ett Visual Basic WPF-projekt). Gör sedan följande:

  1. Dra en Image kontroll från verktygslådan till det övre vänstra hörnet på designytan i designvyn. I textrutan Namn i fönstret Egenskaper namnger du kontrollen "image".

  2. Dra en Button kontroll från verktygslådan till den nedre vänstra delen av programfönstret. I XAML-vyn anger du Content egenskapen för knappen som "Skapa en mosaik" och anger dess Width egenskap som "100". Anslut händelsen Click med händelsehanteraren button_Click som definierats i exemplets kod genom att lägga Click="button_Click" till i elementet <Button> . I textrutan Namn i fönstret Egenskaper namnger du kontrollen "button".

  3. Ersätt hela innehållet i MainWindow.xaml.cs- eller MainWindow.xaml.vb-filen med koden från det här exemplet. För ett C#WPF-projekt kontrollerar du att namnet på arbetsytan matchar projektnamnet.

  4. Exemplet läser JPEG-bilder från en katalog med namnet C:\Users\Public\Pictures\Sample Pictures. Skapa antingen katalogen och placera några bilder i den, eller ändra sökvägen så att den refererar till någon annan katalog som innehåller bilder.

Det här exemplet har vissa begränsningar. Till exempel stöds endast bilder med 32 bitar per bildpunkt. bilder i andra format skadas av BitmapImage objektet under storleksändringen. Källbilderna måste också vara större än panelstorleken. Som en ytterligare övning kan du lägga till funktioner för att hantera flera pixelformat och filstorlekar.