Share via


Kelas System.Threading.Tasks.TaskScheduler

Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.

Kelas TaskScheduler mewakili penjadwal tugas. Penjadwal tugas memastikan bahwa pekerjaan tugas akhirnya dijalankan.

Penjadwal tugas default menyediakan pencurian kerja untuk penyeimbangan beban, injeksi/penghentian utas untuk throughput maksimum, dan performa yang baik secara keseluruhan. Ini harus cukup untuk sebagian besar skenario.

Kelas ini TaskScheduler juga berfungsi sebagai titik ekstensi untuk semua logika penjadwalan yang dapat disesuaikan. Ini termasuk mekanisme seperti cara menjadwalkan tugas untuk eksekusi, dan bagaimana tugas terjadwal harus diekspos ke debugger. Jika Anda memerlukan fungsionalitas khusus, Anda dapat membuat penjadwal kustom dan mengaktifkannya untuk tugas atau kueri tertentu.

Penjadwal tugas default dan kumpulan utas

Penjadwal default untuk Pustaka Paralel Tugas dan PLINQ menggunakan kumpulan utas .NET, yang diwakili oleh ThreadPool kelas, untuk mengantre dan menjalankan pekerjaan. Kumpulan utas menggunakan informasi yang disediakan oleh Task jenis untuk secara efisien mendukung paralelisme yang mendetail (unit kerja berumur pendek) yang sering diwakili oleh tugas dan kueri paralel.

Antrean global vs. antrean lokal

Kumpulan utas mempertahankan antrean kerja FIFO global (first-in, first-out) untuk utas di setiap domain aplikasi. Setiap kali program memanggil ThreadPool.QueueUserWorkItem metode (atau ThreadPool.UnsafeQueueUserWorkItem) , pekerjaan diletakkan pada antrean bersama ini dan akhirnya dibatalkan antreannya ke utas berikutnya yang tersedia. Dimulai dengan .NET Framework 4, antrean ini menggunakan algoritma bebas kunci yang menyerupan ConcurrentQueue<T> kelas. Dengan menggunakan implementasi bebas kunci ini, kumpulan utas menghabiskan lebih sedikit waktu saat mengantre dan membatalkan antrean item kerja. Manfaat performa ini tersedia untuk semua program yang menggunakan kumpulan utas.

Tugas tingkat atas, yang merupakan tugas yang tidak dibuat dalam konteks tugas lain, diletakkan pada antrean global seperti item kerja lainnya. Namun, tugas berlapis atau turunan, yang dibuat dalam konteks tugas lain, ditangani dengan sangat berbeda. Tugas anak atau berlapis diletakkan pada antrean lokal yang khusus untuk utas tempat tugas induk dijalankan. Tugas induk mungkin merupakan tugas tingkat atas atau mungkin juga anak dari tugas lain. Ketika utas ini siap untuk lebih banyak pekerjaan, pertama-tama terlihat dalam antrean lokal. Jika item kerja menunggu di sana, item dapat diakses dengan cepat. Antrean lokal diakses dalam urutan terakhir masuk, pertama keluar (LIFO) untuk mempertahankan lokalitas cache dan mengurangi ketidakcocokan. Untuk informasi selengkapnya tentang tugas anak dan tugas berlapis, lihat Tugas Anak Terlampir dan Terlepas.

Penggunaan antrean lokal tidak hanya mengurangi tekanan pada antrean global, tetapi juga memanfaatkan lokalitas data. Item kerja dalam antrean lokal sering mereferensikan struktur data yang secara fisik dekat satu sama lain dalam memori. Dalam kasus ini, data sudah ada di cache setelah tugas pertama berjalan dan dapat diakses dengan cepat. Baik Parallel LINQ (PLINQ) maupun Parallel kelas menggunakan tugas berlapis dan tugas anak secara ekstensif, dan mencapai speedup yang signifikan dengan menggunakan antrean kerja lokal.

Mencuri pekerjaan

Dimulai dengan .NET Framework 4, kumpulan utas juga memiliki algoritma pencurian kerja untuk membantu memastikan bahwa tidak ada utas yang duduk diam sementara yang lain masih memiliki pekerjaan dalam antrean mereka. Ketika utas kumpulan utas siap untuk lebih banyak pekerjaan, pertama-tama melihat kepala antrean lokalnya, kemudian dalam antrean global, dan kemudian dalam antrean lokal utas lain. Jika menemukan item kerja dalam antrean lokal utas lain, pertama-tama ia menerapkan heuristik untuk memastikan bahwa item tersebut dapat menjalankan pekerjaan secara efisien. Jika bisa, item kerja dibatalkan antreannya dari ekor (dalam urutan FIFO). Ini mengurangi ketidakcocokan pada setiap antrean lokal dan mempertahankan lokalitas data. Arsitektur ini membantu pekerjaan load-balance kumpulan utas lebih efisien daripada versi sebelumnya.

Tugas jangka panjang

Anda mungkin ingin secara eksplisit mencegah tugas dimasukkan ke antrean lokal. Misalnya, Anda mungkin tahu bahwa item kerja tertentu akan berjalan untuk waktu yang relatif lama dan kemungkinan akan memblokir semua item kerja lainnya pada antrean lokal. Dalam hal ini, Anda dapat menentukan System.Threading.Tasks.TaskCreationOptions opsi , yang memberikan petunjuk kepada penjadwal bahwa utas tambahan mungkin diperlukan untuk tugas sehingga tidak memblokir kemajuan maju dari utas lain atau item kerja pada antrean lokal. Dengan menggunakan opsi ini, Anda menghindari kumpulan utas sepenuhnya, termasuk antrean global dan lokal.

Inlining tugas

Dalam beberapa kasus ketika Task ditunggu, itu dapat dijalankan secara sinkron pada utas yang melakukan operasi tunggu. Ini meningkatkan performa dengan mencegah kebutuhan akan utas tambahan dan sebaliknya menggunakan utas yang ada, yang akan diblokir sebaliknya. Untuk mencegah kesalahan karena masuknya kembali, inlining tugas hanya terjadi ketika target tunggu ditemukan di antrean lokal utas yang relevan.

Tentukan konteks sinkronisasi

Anda dapat menggunakan TaskScheduler.FromCurrentSynchronizationContext metode untuk menentukan bahwa tugas harus dijadwalkan untuk dijalankan pada utas tertentu. Ini berguna dalam kerangka kerja seperti Formulir Windows dan Windows Presentation Foundation di mana akses ke objek antarmuka pengguna sering dibatasi untuk kode yang berjalan pada utas yang sama tempat objek UI dibuat.

Contoh berikut menggunakan metode dalam aplikasi Windows Presentation Foundation (WPF) untuk menjadwalkan tugas pada utas TaskScheduler.FromCurrentSynchronizationContext yang sama dengan tempat kontrol antarmuka pengguna (UI) dibuat. Contoh membuat mosaik gambar yang dipilih secara acak dari direktori tertentu. Objek WPF digunakan untuk memuat dan mengubah ukuran gambar. Piksel mentah kemudian diteruskan ke tugas yang menggunakan perulangan For untuk menulis data piksel ke dalam array byte tunggal besar. Tidak diperlukan sinkronisasi karena tidak ada dua petak peta yang menempati elemen array yang sama. Petak peta juga dapat ditulis dalam urutan apa pun karena posisinya dihitung secara independen dari petak peta lainnya. Array besar kemudian diteruskan ke tugas yang berjalan pada utas UI, tempat data piksel dimuat ke dalam kontrol Gambar.

Contoh memindahkan data dari utas UI, memodifikasinya dengan menggunakan perulangan dan Task objek paralel, lalu meneruskannya kembali ke tugas yang berjalan pada utas UI. Pendekatan ini berguna ketika Anda harus menggunakan Pustaka Paralel Tugas untuk melakukan operasi yang tidak didukung oleh WPF API, atau tidak cukup cepat. Cara lain untuk membuat mosaik gambar di WPF adalah dengan menggunakan System.Windows.Controls.WrapPanel kontrol dan menambahkan gambar ke dalamnya. Menangani WrapPanel pekerjaan penempatan petak peta. Namun, pekerjaan ini hanya dapat dilakukan pada utas UI.

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

Untuk membuat contoh, buat proyek aplikasi WPF di Visual Studio dan beri nama WPF_CS1 (untuk proyek C# WPF) atau WPF_VB1 (untuk proyek Visual Basic WPF). Lalu, lakukan hal berikut:

  1. Dalam tampilan desain, seret Image kontrol dari Kotak Alat ke sudut kiri atas permukaan desain. Di kotak teks Nama jendela Properti , beri nama kontrol "gambar".

  2. Button Seret kontrol dari Kotak Alat ke bagian kiri bawah jendela aplikasi. Dalam tampilan XAML, tentukan Content properti tombol sebagai "Buat mosaik", dan tentukan propertinya Width sebagai "100". Click Koneksi peristiwa dengan penanganan aktivitas yang button_Click ditentukan dalam kode contoh dengan menambahkan Click="button_Click" ke <Button> elemen . Di kotak teks Nama jendela Properti , beri nama tombol kontrol "".

  3. Ganti seluruh konten file MainWindow.xaml.cs atau MainWindow.xaml.vb dengan kode dari contoh ini. Untuk proyek C# WPF, pastikan bahwa nama ruang kerja cocok dengan nama proyek.

  4. Contoh membaca gambar JPEG dari direktori bernama C:\Users\Public\Pictures\Sample Pictures. Buat direktori dan tempatkan beberapa gambar di dalamnya, atau ubah jalur untuk merujuk ke beberapa direktori lain yang berisi gambar.

Contoh ini memiliki beberapa batasan. Misalnya, hanya gambar 32-bit per piksel yang didukung; gambar dalam format lain rusak oleh BitmapImage objek selama operasi mengubah ukuran. Selain itu, semua gambar sumber harus lebih besar dari ukuran petak peta. Sebagai latihan lebih lanjut, Anda dapat menambahkan fungsionalitas untuk menangani beberapa format piksel dan ukuran file.