Megosztás a következőn keresztül:


System.Threading.Tasks.TaskScheduler osztály

Ez a cikk kiegészítő megjegyzéseket tartalmaz az API referenciadokumentációjához.

Az TaskScheduler osztály egy feladatütemezőt jelöl. A feladatütemezők biztosítják, hogy egy tevékenység munkája végül végrehajtásra kerül.

Az alapértelmezett feladatütemező munkalopást biztosít a terheléselosztáshoz, a szálinjektáláshoz/kivezetéshez a maximális átviteli sebességhez és az általánosan jó teljesítményhez. A legtöbb forgatókönyvhöz elegendőnek kell lennie.

Az TaskScheduler osztály az összes testreszabható ütemezési logika bővítménypontjaként is szolgál. Ez olyan mechanizmusokat is magában foglal, mint a tevékenységek ütemezése a végrehajtáshoz, és hogy az ütemezett tevékenységek hogyan legyenek kitéve a hibakeresőknek. Ha speciális funkciókra van szüksége, létrehozhat egy egyéni ütemezőt, és engedélyezheti azt adott feladatokhoz vagy lekérdezésekhez.

Az alapértelmezett feladatütemező és a szálkészlet

A párhuzamos feladattár és a PLINQ alapértelmezett ütemezője az osztály által képviselt .NET-szálkészletet használja a ThreadPool feladatok várólistára helyezéséhez és végrehajtásához. A szálkészlet a típus által Task biztosított információk alapján hatékonyan támogatja a párhuzamos feladatok és lekérdezések által gyakran képviselt részletes párhuzamosságot (rövid élettartamú munkaegységeket).

A globális üzenetsor és a helyi üzenetsorok

A szálkészlet minden alkalmazástartományban egy globális FIFO -munkasort (first-in, first-out) tart fenn a szálakhoz. Amikor egy program meghívja a ThreadPool.QueueUserWorkItem (vagy ThreadPool.UnsafeQueueUserWorkItem) metódust, a rendszer ezt a megosztott üzenetsort helyezi el, és végül a következő, elérhetővé váló szálra helyezi. A .NET-keretrendszer 4-től kezdve ez az üzenetsor egy, az ConcurrentQueue<T> osztályhoz hasonló zárolásmentes algoritmust használ. Ezzel a zárolásmentes implementációval a szálkészlet kevesebb időt tölt az üzenetsorok és az üzenetsorok munkaelemeinek megszüntetésekor. Ez a teljesítménybeli előny minden olyan program számára elérhető, amely a szálkészletet használja.

A legfelső szintű tevékenységek, amelyek nem egy másik tevékenység kontextusában lettek létrehozva, ugyanúgy kerülnek a globális várólistára, mint bármely más munkaelemre. A beágyazott vagy gyermek feladatokat azonban, amelyek egy másik tevékenység kontextusában jönnek létre, meglehetősen eltérően kezelik. A gyermek- vagy beágyazott feladat egy helyi üzenetsorra kerül, amely arra a szálra vonatkozik, amelyen a szülőfeladatot végrehajtja. A szülőtevékenység lehet legfelső szintű tevékenység, vagy egy másik tevékenység gyermeke is lehet. Ha ez a szál készen áll a további munkára, először a helyi üzenetsorban jelenik meg. Ha a munkaelemek ott várnak, gyorsan elérhetők. A helyi üzenetsorok a gyorsítótár helyének megőrzése és a versengés csökkentése érdekében az utolsó előtti, első előtti sorrendben (LIFO) érhetők el. További információ a gyermekfeladatokról és a beágyazott feladatokról: Csatolt és leválasztott gyermekfeladatok.

A helyi üzenetsorok használata nem csak csökkenti a globális üzenetsorra nehezedő nyomást, hanem kihasználja az adat helyének előnyeit is. A helyi üzenetsor munkaelemei gyakran hivatkoznak olyan adatstruktúrákra, amelyek fizikailag közel vannak egymáshoz a memóriában. Ezekben az esetekben az adatok már az első feladat futtatása után a gyorsítótárban vannak, és gyorsan elérhetők. A párhuzamos LINQ (PLINQ) és az Parallel osztály egyaránt széles körben használ beágyazott és gyermekfeladatokat, és a helyi munkasorok használatával jelentős gyorsításokat érhet el.

Munkalopás

A .NET-keretrendszer 4-től kezdve a szálkészlet egy munkalopó algoritmust is tartalmaz, amely segít biztosítani, hogy egyetlen szál sem tétlenül üljön, míg mások továbbra is dolgoznak az üzenetsoraikban. Ha egy szálkészlet-szál készen áll a további munkára, először a helyi üzenetsor fejére, majd a globális üzenetsorra, majd a többi szál helyi üzenetsorára néz. Ha egy munkaelemet egy másik szál helyi üzenetsorában talál, először heurisztikus elemeket alkalmaz annak érdekében, hogy hatékonyan tudja futtatni a munkát. Ha lehet, akkor a munkaelemet a farkából (FIFO sorrendben) törli. Ez csökkenti az egyes helyi üzenetsorokon való versengést, és megőrzi az adat honosságát. Ez az architektúra segít a szálkészlet terheléselosztásának hatékonyabb működésében, mint a korábbi verziókban.

Hosszan futó feladatok

Előfordulhat, hogy kifejezetten meg szeretné akadályozni, hogy egy feladat helyi üzenetsorba kerüljön. Előfordulhat például, hogy egy adott munkaelem viszonylag hosszú ideig fog futni, és valószínűleg blokkolja a helyi üzenetsor összes többi munkaelemét. Ebben az esetben megadhatja a System.Threading.Tasks.TaskCreationOptions beállítást, amely arra utal az ütemező számára, hogy a tevékenységhez további szálra lehet szükség, hogy az ne blokkolja a többi szál vagy munkaelem előrehaladtát a helyi üzenetsoron. Ezzel a beállítással teljesen elkerülheti a szálkészletet, beleértve a globális és a helyi üzenetsorokat is.

Tevékenységbesorolás

Bizonyos esetekben, amikor egy Task várakozik, előfordulhat, hogy szinkron módon hajtja végre a várakozási műveletet végrehajtó szálon. Ez növeli a teljesítményt azáltal, hogy megakadályozza egy további szál szükségességét, és ehelyett használja a meglévő szálat, amely egyébként blokkolva lett volna. Az újraküldés miatti hibák elkerülése érdekében a feladatbesorolás csak akkor történik meg, ha a várakozási cél az adott szál helyi üzenetsorában található.

Szinkronizálási környezet megadása

Ezzel a TaskScheduler.FromCurrentSynchronizationContext módszerrel megadhatja, hogy egy tevékenységet ütemezni kell egy adott szálon való futtatásra. Ez olyan keretrendszerekben hasznos, mint a Windows Forms és a Windows megjelenítési alaprendszer, ahol a felhasználói felület objektumaihoz való hozzáférés gyakran a felhasználói felület objektumát létrehozó szálon futó kódra korlátozódik.

Az alábbi példa egy TaskScheduler.FromCurrentSynchronizationContext Windows megjelenítési alaprendszer (WPF) alkalmazás metódusával ütemez egy feladatot ugyanazon a szálon, amelyen a felhasználói felület vezérlője létre lett hozva. A példa egy mozaikképet hoz létre, amely véletlenszerűen van kiválasztva egy adott könyvtárból. A WPF-objektumok a képek betöltésére és átméretezésére szolgálnak. A nyers képpontok ezután egy olyan feladatnak lesznek átadva, amely hurkot For használ a képpontadatok nagy egy bájtos tömbbe való írásához. Nincs szükség szinkronizálásra, mert két csempe sem foglalja el ugyanazokat a tömbelemeket. A csempék bármilyen sorrendben megírhatók, mert a pozíciójuk a többi csempétől függetlenül számítható ki. A nagyméretű tömb ezután egy felhasználói felületen futó feladatnak lesz átadva, ahol a képpontadatok betöltődnek egy képvezérlőbe.

A példa áthelyezi az adatokat a felhasználói felületi szálról, párhuzamos hurkok és Task objektumok használatával módosítja őket, majd átadja azokat egy, a felhasználói felületen futó feladatnak. Ez a megközelítés akkor hasznos, ha a feladat párhuzamos kódtárával olyan műveleteket kell végrehajtania, amelyeket a WPF API nem támogat, vagy nem elég gyorsak. Képmozaik létrehozásának másik módja a WPF-ben egy vezérlő használata System.Windows.Controls.WrapPanel és képek hozzáadása. A WrapPanel csempék elhelyezésének munkáját a kezelő kezeli. Ez a munka azonban csak a felhasználói felületen végezhető el.

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

A példa létrehozásához hozzon létre egy WPF-alkalmazásprojektet a Visual Studióban, és nevezze el WPF_CS1 (C# WPF-projekt esetén) vagy WPF_VB1 (Visual Basic WPF-projekt esetén). Ezután tegye a következőket:

  1. Tervező nézetben húzzon egy vezérlőt Image az Eszközkészletből a tervezőfelület bal felső sarkába. A Tulajdonságok ablak Név szövegmezőjében adja meg a vezérlő "képének" nevét.

  2. Húzza a vezérlőt Button az eszközkészletből az alkalmazás ablakának bal alsó részére. XAML nézetben adja meg a Content gomb tulajdonságát "Mozaik készítése" értékként, és adja meg a tulajdonságát Width "100" értékként. Csatlakozás az eseményt Click a button_Click példa kódjában definiált eseménykezelővel az <Button> elem hozzáadásávalClick="button_Click". A Tulajdonságok ablak Név szövegmezőjében adja meg a vezérlő "gombját".

  3. Cserélje le a MainWindow.xaml.cs vagy MainWindow.xaml.vb fájl teljes tartalmát a példában szereplő kódra. C# WPF-projekt esetén győződjön meg arról, hogy a munkaterület neve megegyezik a projekt nevével.

  4. A példa JPEG-képeket olvas be egy C:\Users\Public\Pictures\Sample Pictures nevű könyvtárból. Hozza létre a könyvtárat, és helyezzen bele néhány képet, vagy módosítsa az elérési utat, hogy más, képeket tartalmazó könyvtárra hivatkozzon.

Ez a példa bizonyos korlátozásokkal rendelkezik. Például csak a képpontonként 32 bites képek támogatottak; más formátumú képeket az BitmapImage objektum az átméretezési művelet során megsérül. Emellett a forráslemezképek méretének nagyobbnak kell lennie, mint a csempe mérete. További gyakorlatként több képpontformátumot és fájlméretet is kezelhet funkciókkal.