System.Threading.Tasks.TaskScheduler – třída

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Třída TaskScheduler představuje plánovač úloh. Plánovač úkolů zajistí, že se nakonec spustí práce úkolu.

Výchozí plánovač úloh poskytuje krádeže práce pro vyrovnávání zatížení, vkládání vláken nebo vyřazení z provozu pro maximální propustnost a celkový dobrý výkon. Pro většinu scénářů by mělo být dostatečné.

Třída TaskScheduler také slouží jako bod rozšíření pro všechny přizpůsobitelné plánování logiky. To zahrnuje mechanismy, jako je naplánování úlohy pro provádění a jak by měly být naplánované úlohy zpřístupněny ladicím programům. Pokud potřebujete speciální funkce, můžete vytvořit vlastní plánovač a povolit ho pro konkrétní úlohy nebo dotazy.

Výchozí plánovač úloh a fond vláken

Výchozí plánovač pro paralelní knihovnu úloh a PLINQ používá fond vláken .NET, který je reprezentován ThreadPool třídou, k frontě a spuštění práce. Fond vláken používá informace poskytované typem Task k efektivní podpoře jemně odstupňovaného paralelismu (krátkodobé jednotky práce), které často představují paralelní úlohy a dotazy.

Globální fronta vs. místní fronty

Fond vláken udržuje globální pracovní frontu FIFO (first-in, first-out) pro vlákna v každé doméně aplikace. Kdykoli program zavolá metodu ThreadPool.QueueUserWorkItem (nebo ThreadPool.UnsafeQueueUserWorkItem), práce se umístí do této sdílené fronty a nakonec se zruší ve frontě na další vlákno, které bude k dispozici. Počínaje rozhraním .NET Framework 4 tato fronta používá algoritmus bez zámku, který se podobá ConcurrentQueue<T> třídě. Díky použití této implementace bez uzamčení fond vláken stráví méně času, když zařadí do fronty a zruší fronty pracovních položek. Tato výhoda výkonu je k dispozici pro všechny programy, které používají fond vláken.

Úkoly nejvyšší úrovně, které jsou úkoly, které nejsou vytvořeny v kontextu jiného úkolu, se umístí do globální fronty stejně jako všechny ostatní pracovní položky. Vnořené nebo podřízené úlohy, které jsou vytvořeny v kontextu jiného úkolu, se ale zpracovávají poměrně odlišně. Podřízená nebo vnořená úloha se umístí do místní fronty, která je specifická pro vlákno, na kterém je spuštěna nadřazená úloha. Nadřazený úkol může být úkol nejvyšší úrovně nebo také podřízeným úkolem jiného úkolu. Až bude toto vlákno připravené na další práci, nejprve se podívá do místní fronty. Pokud tam čekají pracovní položky, můžou se k nim dostat rychle. Místní fronty se přistupují k místním frontám v posledním pořadí (LIFO), aby se zachovala lokalita mezipaměti a snížila kolize. Další informace o podřízených úkolech a vnořených úkolech naleznete v tématu Připojené a odpojené podřízené úkoly.

Použití místních front nejen snižuje tlak na globální frontu, ale také využívá umístění dat. Pracovní položky v místní frontě často odkazují na datové struktury, které jsou fyzicky blízko sebe v paměti. V těchto případech jsou data již v mezipaměti po spuštění prvního úkolu a dají se k němu rychle získat přístup. Paralelní LINQ (PLINQ) i Parallel třída používají vnořené úlohy a podřízené úlohy a výrazně zrychlit pomocí místních pracovních front.

Krádež práce

Počínaje rozhraním .NET Framework 4 obsahuje fond vláken také algoritmus krádeže práce, který pomáhá zajistit, aby žádná vlákna nebyla nečinná, zatímco ostatní stále pracují ve svých frontách. Když je vlákno fondu vláken připravené na další práci, nejprve se podívá na hlavu místní fronty, pak v globální frontě a potom v místních frontách jiných vláken. Pokud najde pracovní položku v místní frontě jiného vlákna, nejprve použije heuristika, aby se zajistilo, že může pracovat efektivně. Pokud ano, zruší frontu pracovní položky z chvostu (v pořadí FIFO). Tím se sníží kolize v každé místní frontě a zachová se umístění dat. Tato architektura pomáhá vyrovnávání zatížení fondu vláken efektivněji než předchozí verze.

Dlouhotrvající úlohy

Můžete chtít explicitně zabránit tomu, aby se úloha umístila do místní fronty. Můžete například vědět, že konkrétní pracovní položka bude běžet poměrně dlouho a pravděpodobně zablokuje všechny ostatní pracovní položky v místní frontě. V tomto případě můžete zadat System.Threading.Tasks.TaskCreationOptions možnost, která plánovači poskytne nápovědu, že pro úlohu může být vyžadováno další vlákno, aby neblokoval průběh předávání jiných vláken nebo pracovních položek v místní frontě. Pomocí této možnosti se úplně vyhněte fondu vláken, včetně globálních a místních front.

Vkládání úkolů

V některých případech, kdy Task je čekání na, může být spuštěn synchronně ve vlákně, které provádí operaci čekání. Tím se zvýší výkon tím, že zabráníte nutnosti dalšího vlákna a místo toho použijete existující vlákno, které by jinak zablokovalo. Aby se zabránilo chybám způsobeným opětovnou architekturou, inlineace úloh nastane pouze v případě, že se cíl čekání najde v místní frontě příslušného vlákna.

Zadání kontextu synchronizace

Metodu TaskScheduler.FromCurrentSynchronizationContext můžete použít k určení, že má být úloha naplánována tak, aby běžela v určitém vlákně. To je užitečné v architekturách, jako jsou model Windows Forms a Windows Presentation Foundation, kde je přístup k objektům uživatelského rozhraní často omezen na kód spuštěný ve stejném vlákně, na kterém byl objekt uživatelského rozhraní vytvořen.

Následující příklad používá metodu TaskScheduler.FromCurrentSynchronizationContext v aplikaci WPF (Windows Presentation Foundation) k naplánování úlohy ve stejném vlákně, ve které byl vytvořen ovládací prvek uživatelského rozhraní (UI). Příklad vytvoří mozaiku obrázků, které jsou náhodně vybrány ze zadaného adresáře. Objekty WPF slouží k načtení a změně velikosti obrázků. Nezpracované pixely se pak předají úloze, která pomocí For smyčky zapisuje data pixelů do velkého pole s jedním bajtem. Nevyžaduje se žádná synchronizace, protože žádné dvě dlaždice zabírají stejné prvky pole. Dlaždice lze také zapsat v libovolném pořadí, protože jejich pozice se počítá nezávisle na jakékoli jiné dlaždici. Velké pole se pak předá úloze, která běží ve vlákně uživatelského rozhraní, kde se data pixelů načtou do ovládacího prvku Obrázek.

Příklad přesune data z vlákna uživatelského rozhraní, upraví je pomocí paralelních smyček a Task objektů a pak je předá zpět úloze, která běží ve vlákně uživatelského rozhraní. Tento přístup je užitečný v případě, že k provádění operací, které rozhraní WPF API nepodporuje, musíte použít paralelní knihovnu úloh nebo nejsou dostatečně rychlé. Dalším způsobem, jak vytvořit obrázkovou mozaiku ve WPF, je použít System.Windows.Controls.WrapPanel ovládací prvek a přidat do něj obrázky. Zpracovává WrapPanel práci umístění dlaždic. Tuto práci však lze provést pouze ve vlákně uživatelského rozhraní.

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

Pokud chcete vytvořit příklad, vytvořte projekt aplikace WPF v sadě Visual Studio a pojmenujte ho WPF_CS1 (pro projekt WPF jazyka C#) nebo WPF_VB1 (pro projekt WPF jazyka Visual Basic). Potom udělejte následující:

  1. V návrhovém zobrazení přetáhněte Image ovládací prvek ze sady nástrojů do levého horního rohu návrhové plochy. V textovém poli Název okna Vlastnosti pojmenujte ovládací prvek "image".

  2. Button Přetáhněte ovládací prvek ze sady nástrojů do levé dolní části okna aplikace. V zobrazení XAML zadejte Content vlastnost tlačítka jako "Vytvořit mozaiku" a zadejte její Width vlastnost jako "100". Click Připojení událost s obslužnou rutinou button_Click události definovanou v kódu příkladu přidáním Click="button_Click" do elementu<Button>. V textovém poli Název okna Vlastnosti pojmenujte ovládací prvek "button".

  3. Celý obsah souboru MainWindow.xaml.cs nebo MainWindow.xaml.vb nahraďte kódem z tohoto příkladu. V případě projektu WPF jazyka C# se ujistěte, že název pracovního prostoru odpovídá názvu projektu.

  4. Příklad načte obrázky JPEG z adresáře S názvem C:\Users\Public\Pictures\Sample Pictures. Buď vytvořte adresář a umístěte do něj nějaké obrázky, nebo změňte cestu tak, aby odkazovat na jiný adresář, který obsahuje obrázky.

Tento příklad má určitá omezení. Podporují se například pouze 32bitové obrázky na pixel; obrázky v jiných formátech jsou objektem BitmapImage poškozeny během operace změny velikosti. Zdrojové image musí být také větší než velikost dlaždice. V dalším cvičení můžete přidat funkce pro zpracování více pixelů formátů a velikostí souborů.