System.Threading.Tasks.TaskScheduler, klasa

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Klasa TaskScheduler reprezentuje harmonogram zadań. Harmonogram zadań gwarantuje, że praca zadania zostanie ostatecznie wykonana.

Domyślny harmonogram zadań zapewnia kradzież pracy na potrzeby równoważenia obciążenia, iniekcji wątku/wycofywania w celu uzyskania maksymalnej przepływności oraz ogólnej dobrej wydajności. Powinno to być wystarczające w przypadku większości scenariuszy.

Klasa TaskScheduler służy również jako punkt rozszerzenia dla wszystkich dostosowywalnych logiki planowania. Obejmuje to mechanizmy, takie jak planowanie zadania pod kątem wykonywania oraz sposób uwidaczniania zaplanowanych zadań dla debugerów. Jeśli potrzebujesz specjalnej funkcjonalności, możesz utworzyć niestandardowy harmonogram i włączyć go dla określonych zadań lub zapytań.

Domyślny harmonogram zadań i pula wątków

Domyślny harmonogram dla biblioteki równoległej zadań i PLINQ używa puli wątków platformy .NET reprezentowanej przez klasę ThreadPool do kolejkowania i wykonywania pracy. Pula wątków używa informacji dostarczanych przez Task typ, aby efektywnie obsługiwać szczegółowe równoległości (krótkotrwałe jednostki pracy), które często reprezentują zadania równoległe i zapytania.

Kolejka globalna a kolejki lokalne

Pula wątków obsługuje globalną kolejkę roboczą FIFO (first-in, first-out) dla wątków w każdej domenie aplikacji. Za każdym razem, gdy program wywołuje metodę ThreadPool.QueueUserWorkItem (lub ThreadPool.UnsafeQueueUserWorkItem), praca jest umieszczana w tej udostępnionej kolejce, a ostatecznie zostanie co najmniej usunięta z kolejki do następnego wątku, który stanie się dostępny. Począwszy od programu .NET Framework 4, ta kolejka używa algorytmu bez blokady przypominającego klasę ConcurrentQueue<T> . Dzięki tej implementacji bez blokady pula wątków poświęca mniej czasu, gdy kolejkuje i usuwa elementy robocze. Ta korzyść z wydajności jest dostępna dla wszystkich programów korzystających z puli wątków.

Zadania najwyższego poziomu, które są zadaniami, które nie są tworzone w kontekście innego zadania, są umieszczane w kolejce globalnej tak samo jak w przypadku każdego innego elementu roboczego. Jednak zagnieżdżone lub podrzędne zadania, które są tworzone w kontekście innego zadania, są obsługiwane zupełnie inaczej. Podrzędne lub zagnieżdżone zadanie jest umieszczane w lokalnej kolejce specyficznej dla wątku, na którym jest wykonywane zadanie nadrzędne. Zadanie nadrzędne może być zadaniem najwyższego poziomu lub może być również elementem podrzędnym innego zadania. Gdy ten wątek jest gotowy do większej ilości pracy, najpierw wygląda w kolejce lokalnej. Jeśli elementy robocze oczekują tam, można uzyskać do nich szybki dostęp. Dostęp do kolejek lokalnych jest uzyskiwany w kolejności ostatniego, pierwszego wyjścia (LIFO), aby zachować lokalność pamięci podręcznej i zmniejszyć rywalizację. Aby uzyskać więcej informacji o zadaniach podrzędnych i zadaniach zagnieżdżonych, zobacz Dołączone i Odłączone zadania podrzędne.

Korzystanie z kolejek lokalnych nie tylko zmniejsza presję na kolejkę globalną, ale także wykorzystuje lokalność danych. Elementy robocze w kolejce lokalnej często odwołują się do struktur danych, które są fizycznie blisko siebie w pamięci. W takich przypadkach dane są już w pamięci podręcznej po uruchomieniu pierwszego zadania i można uzyskać do nich szybki dostęp. Zarówno równoległe LINQ (PLINQ), jak i Parallel klasa używają zagnieżdżonych zadań i zadań podrzędnych w szerokim zakresie, a także osiągają znaczne przyspieszenie przy użyciu lokalnych kolejek roboczych.

Kradzież pracy

Począwszy od programu .NET Framework 4, pula wątków zawiera również algorytm kradzieży pracy, aby upewnić się, że żadne wątki nie są w stanie bezczynności, podczas gdy inni nadal pracują w kolejkach. Gdy wątek puli wątków jest gotowy do większej ilości pracy, najpierw patrzy na głowę swojej kolejki lokalnej, a następnie w kolejce globalnej, a następnie w lokalnych kolejkach innych wątków. Jeśli znajdzie element roboczy w lokalnej kolejce innego wątku, najpierw stosuje heurystyki, aby upewnić się, że może działać wydajnie. Jeśli to możliwe, usuwa kolejki elementu roboczego z ogona (w kolejności FIFO). Zmniejsza to rywalizację o każdą kolejkę lokalną i zachowuje lokalność danych. Ta architektura ułatwia wydajniejsze równoważenie obciążenia puli wątków niż wcześniejsze wersje.

Długotrwałe zadania

Możesz jawnie uniemożliwić umieszczanie zadania w kolejce lokalnej. Na przykład możesz wiedzieć, że określony element roboczy będzie uruchamiany przez stosunkowo długi czas i prawdopodobnie zablokuje wszystkie inne elementy robocze w kolejce lokalnej. W takim przypadku można określić System.Threading.Tasks.TaskCreationOptions opcję, która zawiera wskazówkę dla harmonogramu, że dodatkowy wątek może być wymagany dla zadania, aby nie blokował postępu przekazywania innych wątków ani elementów roboczych w kolejce lokalnej. Korzystając z tej opcji, należy całkowicie unikać puli wątków, w tym kolejek globalnych i lokalnych.

Podkreślenie zadania

W niektórych przypadkach, gdy element Task jest czekany, może być wykonywany synchronicznie w wątku wykonującym operację oczekiwania. Zwiększa to wydajność, zapobiegając konieczności użycia dodatkowego wątku, a zamiast tego przy użyciu istniejącego wątku, który zablokowałby się w przeciwnym razie. Aby zapobiec błędom spowodowanym ponownym uruchomieniem, podwładanie zadań występuje tylko wtedy, gdy element docelowy oczekiwania zostanie znaleziony w lokalnej kolejce odpowiedniego wątku.

Określanie kontekstu synchronizacji

Można użyć TaskScheduler.FromCurrentSynchronizationContext metody , aby określić, że zadanie powinno być zaplanowane do uruchomienia w określonym wątku. Jest to przydatne w strukturach, takich jak Windows Forms i Windows Presentation Foundation, gdzie dostęp do obiektów interfejsu użytkownika jest często ograniczony do kodu działającego w tym samym wątku, w którym utworzono obiekt interfejsu użytkownika.

W poniższym przykładzie użyto TaskScheduler.FromCurrentSynchronizationContext metody w aplikacji Windows Presentation Foundation (WPF), aby zaplanować zadanie w tym samym wątku, na który utworzono kontrolkę interfejsu użytkownika. W przykładzie tworzona jest mozaika obrazów, które są losowo wybierane z określonego katalogu. Obiekty WPF są używane do ładowania i zmieniania rozmiaru obrazów. Nieprzetworzone piksele są następnie przekazywane do zadania, które używa For pętli do zapisywania danych pikseli w dużej tablicy jednobajtowej. Synchronizacja nie jest wymagana, ponieważ żadne dwa kafelki nie zajmują tych samych elementów tablicy. Kafelki można również zapisywać w dowolnej kolejności, ponieważ ich położenie jest obliczane niezależnie od dowolnego innego kafelka. Duża tablica jest następnie przekazywana do zadania uruchamianego w wątku interfejsu użytkownika, w którym dane pikseli są ładowane do kontrolki Obraz.

Przykład przenosi dane z wątku interfejsu użytkownika, modyfikuje je przy użyciu pętli równoległych i Task obiektów, a następnie przekazuje je z powrotem do zadania uruchamianego w wątku interfejsu użytkownika. Takie podejście jest przydatne, gdy musisz użyć biblioteki równoległej zadań do wykonywania operacji, które nie są obsługiwane przez interfejs API WPF lub nie są wystarczająco szybkie. Innym sposobem utworzenia mozaiki obrazu w WPF jest użycie System.Windows.Controls.WrapPanel kontrolki i dodanie do niej obrazów. Uchwyty WrapPanel pracy pozycjonowania kafelków. Tę pracę można jednak wykonać tylko w wątku interfejsu użytkownika.

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

Aby utworzyć przykład, utwórz projekt aplikacji WPF w programie Visual Studio i nadaj mu nazwę WPF_CS1 (dla projektu C# WPF) lub WPF_VB1 (dla projektu Visual Basic WPF). Następnie wykonaj poniższe czynności:

  1. W widoku projektu przeciągnij kontrolkę Image z przybornika do lewego górnego rogu powierzchni projektowej. W polu tekstowym Nazwa okna Właściwości nadaj kontrolce nazwę "image".

  2. Przeciągnij kontrolkę Button z przybornika do lewej dolnej części okna aplikacji. W widoku XAML określ Content właściwość przycisku jako "Utwórz mozaikę" i określ jej Width właściwość jako "100". Połączenie zdarzenia Click z procedurą obsługi zdarzeń button_Click zdefiniowaną w kodzie przykładu <Button> przez dodanie Click="button_Click" go do elementu. W polu tekstowym Nazwa okna Właściwości nadaj kontrolce nazwę "button".

  3. Zastąp całą zawartość pliku MainWindow.xaml.cs lub MainWindow.xaml.vb kodem z tego przykładu. W przypadku projektu WPF języka C# upewnij się, że nazwa obszaru roboczego jest zgodna z nazwą projektu.

  4. Przykład odczytuje obrazy JPEG z katalogu o nazwie C:\Users\Public\Pictures\Sample Pictures. Utwórz katalog i umieść w nim kilka obrazów lub zmień ścieżkę, aby odwołać się do innego katalogu zawierającego obrazy.

Ten przykład ma pewne ograniczenia. Na przykład obsługiwane są tylko obrazy 32-bitowe na piksel; obrazy w innych formatach są uszkodzone przez BitmapImage obiekt podczas operacji zmiany rozmiaru. Ponadto wszystkie obrazy źródłowe muszą być większe niż rozmiar kafelka. W kolejnym ćwiczeniu możesz dodać funkcje do obsługi wielu formatów pikseli i rozmiarów plików.