System.Threading.Tasks.TaskScheduler sınıfı

Bu makale, bu API'nin başvuru belgelerine ek açıklamalar sağlar.

TaskScheduler sınıfı bir görev zamanlayıcısını temsil eder. Görev zamanlayıcı, bir görevin çalışmasının sonunda yürütülmesini sağlar.

Varsayılan görev zamanlayıcı yük dengeleme için iş çalma, maksimum aktarım hızı için iş parçacığı ekleme/kullanımdan kaldırma ve genel olarak iyi performans sağlar. Çoğu senaryo için yeterli olmalıdır.

sınıfı TaskScheduler ayrıca tüm özelleştirilebilir zamanlama mantığı için uzantı noktası görevi görür. Bu, bir görevin yürütülmesi için nasıl zamanlandığı ve zamanlanmış görevlerin hata ayıklayıcılara nasıl gösterileceği gibi mekanizmaları içerir. Özel işlevlere ihtiyacınız varsa, özel bir zamanlayıcı oluşturabilir ve bunu belirli görevler veya sorgular için etkinleştirebilirsiniz.

Varsayılan görev zamanlayıcı ve iş parçacığı havuzu

Görev Paralel Kitaplığı ve PLINQ için varsayılan zamanlayıcı, işi kuyruğa almak ve yürütmek için sınıfı tarafından ThreadPool temsil edilen .NET iş parçacığı havuzunu kullanır. İş parçacığı havuzu, paralel görevlerin ve sorguların Task sıklıkla temsil ettiği ayrıntılı paralelliği (kısa süreli iş birimleri) verimli bir şekilde desteklemek için türü tarafından sağlanan bilgileri kullanır.

Genel kuyruk ile yerel kuyruklar karşılaştırması

İş parçacığı havuzu, her uygulama etki alanındaki iş parçacıkları için genel bir FIFO (ilk gelen ilk çıkar) iş kuyruğu tutar. Bir program (veya ThreadPool.UnsafeQueueUserWorkItem) yöntemini her çağırışında ThreadPool.QueueUserWorkItem , iş bu paylaşılan kuyruğa alınır ve sonunda kullanılabilir hale gelen bir sonraki iş parçacığında kuyruğa alınır. .NET Framework 4'den başlayarak bu kuyruk, sınıfına ConcurrentQueue<T> benzeyen bir kilitsiz algoritma kullanır. İş parçacığı havuzu, bu kilitsiz uygulamayı kullanarak iş öğelerini kuyruğa alıp kuyruklarından çıkardığında daha az zaman harcar. Bu performans avantajı, iş parçacığı havuzunu kullanan tüm programlar tarafından kullanılabilir.

Başka bir görev bağlamında oluşturulmayan görevler olan üst düzey görevler, diğer iş öğeleri gibi genel kuyruğa konur. Ancak, başka bir görev bağlamında oluşturulan iç içe veya alt görevler oldukça farklı işlenir. Alt veya iç içe görev, üst görevin yürütülmekte olduğu iş parçacığına özgü yerel bir kuyruğa konur. Üst görev üst düzey bir görev veya başka bir görevin alt görevi olabilir. Bu iş parçacığı daha fazla çalışmaya hazır olduğunda, önce yerel kuyruğa bakar. İş öğeleri orada bekliyorsa, bunlara hızlı bir şekilde erişilebilir. Önbellek yerelliğini korumak ve çekişmeleri azaltmak için yerel kuyruklara son gelen ilk çıkış sırasında (LIFO) erişilir. Alt görevler ve iç içe görevler hakkında daha fazla bilgi için bkz . Ekli ve Ayrılmış Alt Görevler.

Yerel kuyrukların kullanılması yalnızca genel kuyruk üzerindeki baskıyı azaltmakla kalmaz, aynı zamanda veri yerelliğinden de yararlanır. Yerel kuyruktaki iş öğeleri genellikle bellekte fiziksel olarak birbirine yakın olan veri yapılarına başvurur. Bu gibi durumlarda, ilk görev çalıştırıldıktan sonra veriler zaten önbellektedir ve bunlara hızlı bir şekilde erişilebilir. Hem Paralel LINQ (PLINQ) hem Parallel de sınıfı iç içe görevleri ve alt görevleri yoğun bir şekilde kullanır ve yerel iş kuyruklarını kullanarak önemli hızlar elde eder.

İş çalma

.NET Framework 4'den başlayarak iş parçacığı havuzu, başkalarının kuyruklarında çalışmaya devam ederken hiçbir iş parçacığının boşta kalmadığından emin olmak için bir iş parçacığı çalma algoritması da içerir. İş parçacığı havuzu iş parçacığı daha fazla çalışmaya hazır olduğunda, önce yerel kuyruğunun başına, sonra genel kuyrukta ve ardından diğer iş parçacıklarının yerel kuyruklarına bakar. Başka bir iş parçacığının yerel kuyruğunda bir iş öğesi bulursa, önce işi verimli bir şekilde çalıştıradığından emin olmak için buluşsal yöntemler uygular. Bunu yapabilirse, iş öğesini kuyruktan (FIFO sırasına göre) kaldırır. Bu, her yerel kuyrukta çekişmesini azaltır ve veri yerelliğini korur. Bu mimari, iş parçacığı havuzu yük dengelemesinin önceki sürümlerden daha verimli çalışmasına yardımcı olur.

Uzun süre çalışan görevler

Bir görevin yerel kuyruğa alınmasını açıkça engellemek isteyebilirsiniz. Örneğin, belirli bir iş öğesinin nispeten uzun süre çalışacağını ve büyük olasılıkla yerel kuyruktaki diğer tüm iş öğelerini engelleyebileceğini biliyor olabilirsiniz. Bu durumda, zamanlayıcıya System.Threading.Tasks.TaskCreationOptions görev için ek bir iş parçacığı gerekebileceğine dair bir ipucu sağlayan seçeneğini belirtebilirsiniz, böylece yerel kuyruktaki diğer iş parçacıklarının veya iş öğelerinin ilerleme durumunu engellemez. Bu seçeneği kullanarak, genel ve yerel kuyruklar dahil olmak üzere iş parçacığı havuzundan tamamen kaçınabilirsiniz.

Görev inlining

Bazı durumlarda, bekleme Task işlemi beklenirken bekleme işlemini gerçekleştiren iş parçacığında zaman uyumlu olarak yürütülebilir. Bu, ek iş parçacığı gereksinimini önleyerek ve bunun yerine aksi takdirde engellenen mevcut iş parçacığını kullanarak performansı artırır. Yeniden giriş nedeniyle oluşan hataları önlemek için, görev inlining yalnızca ilgili iş parçacığının yerel kuyruğunda bekleme hedefi bulunduğunda gerçekleşir.

Eşitleme bağlamı belirtme

Bir görevin belirli bir iş parçacığında çalışacak şekilde zamanlanması gerektiğini belirtmek için yöntemini kullanabilirsiniz TaskScheduler.FromCurrentSynchronizationContext . Bu, kullanıcı arabirimi nesnelerine erişimin genellikle ui nesnesinin oluşturulduğu iş parçacığında çalışan kodla sınırlı olduğu Windows Forms ve Windows Presentation Foundation gibi çerçevelerde kullanışlıdır.

Aşağıdaki örnek, kullanıcı arabirimi (UI) denetiminin TaskScheduler.FromCurrentSynchronizationContext oluşturulduğu iş parçacığında bir görev zamanlamak için bir Windows Presentation Foundation (WPF) uygulamasında yöntemini kullanır. Örnek, belirtilen dizinden rastgele seçilen görüntülerin mozaiğini oluşturur. WPF nesneleri görüntüleri yüklemek ve yeniden boyutlandırmak için kullanılır. Ham pikseller daha sonra piksel verilerini büyük bir tek baytlık diziye yazmak için döngü kullanan For bir göreve geçirilir. İki kutucuk aynı dizi öğelerini kaplamadığı için eşitleme gerekmez. Konumları diğer kutucuklardan bağımsız olarak hesaplandığından kutucuklar herhangi bir sırayla da yazılabilir. Daha sonra büyük dizi, piksel verilerinin görüntü denetimine yüklendiği UI iş parçacığında çalışan bir göreve geçirilir.

Örnek, verileri UI iş parçacığından taşır, paralel döngüler ve Task nesneler kullanarak değiştirir ve sonra ui iş parçacığında çalışan bir göreve geri geçirir. Bu yaklaşım, WPF API tarafından desteklenmeyen veya yeterince hızlı olmayan işlemleri gerçekleştirmek için Görev Paralel Kitaplığı'nı kullanmanız gerektiğinde kullanışlıdır. WPF'de görüntü mozaiği oluşturmanın bir diğer yolu da bir System.Windows.Controls.WrapPanel denetim kullanmak ve buna görüntü eklemektir. , WrapPanel kutucukları konumlandırma işini işler. Ancak, bu iş yalnızca kullanıcı arabirimi iş parçacığında gerçekleştirilebilir.

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

Örneği oluşturmak için Visual Studio'da bir WPF uygulama projesi oluşturun ve bunu WPF_CS1 (C# WPF projesi için) veya WPF_VB1 (Visual Basic WPF projesi için) adlandırın. Ardından şunları yapın:

  1. Tasarım görünümünde, Araç Kutusu'ndan bir Image denetimi tasarım yüzeyinin sol üst köşesine sürükleyin. Özellikler penceresinin Ad metin kutusunda denetimi "görüntü" olarak adlandırın.

  2. Araç Kutusu'ndan bir Button denetimi uygulama penceresinin sol alt kısmına sürükleyin. XAML görünümünde düğmenin Content özelliğini "Mozaik yap" olarak belirtin ve özelliğini "100" olarak belirtin Width . Click öğesini ekleyerek Click="button_Click"<Button> örneğin kodunda tanımlanan olay işleyicisi ile button_Click olayı Bağlan. Özellikler penceresinin Ad metin kutusunda denetimi "düğme" olarak adlandırın.

  3. MainWindow.xaml.cs veya MainWindow.xaml.vb dosyasının tüm içeriğini bu örnekteki kodla değiştirin. C# WPF projesi için, çalışma alanının adının proje adıyla eşleştiğinden emin olun.

  4. Örnek, C:\Users\Public\Pictures\Sample Pictures adlı dizinden JPEG görüntülerini okur. Dizini oluşturun ve bazı görüntüler yerleştirin veya yolu görüntü içeren başka bir dizine başvuracak şekilde değiştirin.

Bu örnekte bazı sınırlamalar vardır. Örneğin, yalnızca piksel başına 32 bit görüntüler desteklenir; Diğer biçimlerdeki görüntüler, yeniden boyutlandırma işlemi sırasında nesne tarafından BitmapImage bozulur. Ayrıca, kaynak görüntülerin tümü kutucuk boyutundan büyük olmalıdır. Daha fazla alıştırmada birden çok piksel biçimini ve dosya boyutunu işlemek için işlevsellik ekleyebilirsiniz.