System.Threading.Tasks.TaskScheduler 类

本文提供了此 API 参考文档的补充说明。

TaskScheduler 类表示任务计划程序。 任务计划程序确保最终执行任务作业。

默认任务计划程序为负载均衡、线程注入/停用提供最大吞吐量和总体良好性能的工作窃取。 它应足以满足大多数方案。

TaskScheduler 类还充当所有可自定义计划逻辑的扩展点。 这包括如何计划任务以供执行的机制,以及如何向调试器公开计划任务。 如果需要特殊功能,可以创建自定义计划程序,并为特定任务或查询启用它。

默认任务计划程序和线程池

任务并行库和 PLINQ 的默认计划程序使用由类表示 ThreadPool 的 .NET 线程池来排队和执行工作。 线程池使用类型提供的信息 Task ,以有效支持并行任务和查询通常表示的细粒度并行度(生存期较短的工作单位)。

全局队列与本地队列

线程池维护每个应用程序域中线程的全局 FIFO(先入先出)工作队列。 每当程序调用 ThreadPool.QueueUserWorkItem (或 ThreadPool.UnsafeQueueUserWorkItem) 方法时,工作就会放在此共享队列上,并最终取消排队到下一个可用线程。 从 .NET Framework 4 开始,此队列使用类似于类的 ConcurrentQueue<T> 无锁算法。 使用此无锁实现,线程池在排队和取消队列工作项时花费的时间更少。 此性能优势可用于使用线程池的所有程序。

最高级任务(即不在其他任务的上下文中创建的任务)与任何其他工作项一样放在全局队列上。 但是,嵌套任务或子任务(在其他任务的上下文中创建)的处理方式大不相同。 子任务或嵌套任务放置在特定于执行父任务的线程的本地队列上。 父任务可能是最高级任务,也可能是其他任务的子任务。 当此线程准备好执行更多工作时,首先查看本地队列。 如果工作项在此处等待,即可快速访问它们。 本地队列在最后一出顺序(LIFO)中访问,以保留缓存区域并减少争用。 有关子任务和嵌套任务的详细信息,请参阅 “附加”和“分离的子任务”。

使用本地队列不仅会降低全局队列的压力,而且还利用数据区域。 本地队列中的工作项经常引用内存中物理上彼此接近的数据结构。 在这些情况下,第一个任务运行后,数据已在缓存中,可以快速访问。 并行 LINQ (PLINQ)Parallel类都广泛使用嵌套任务和子任务,并使用本地工作队列实现显著加速。

工作窃取

从 .NET Framework 4 开始,线程池还具有工作窃取算法,以帮助确保没有线程处于空闲状态,而其他人仍在其队列中工作。 当线程池线程准备好执更多工作时,首先查看其本地队列的开头,再查看全局队列,然后查看其他线程的本地队列。 如果在其他线程的本地队列中找到工作项,它会先应用试探法以确保可有效运行工作。 如果可以,它将从尾部取消工作项的排队(按 FIFO 顺序)。 这样可以减少每个本地队列上的争用并保留数据位置。 此体系结构可帮助线程池负载均衡比以前版本更高效地工作。

长时间运行的任务

可能想要显式防止将任务放到本地队列上。 例如,你可能知道特定工作项将运行相对长的时间并可能阻塞本地队列中的所有其他工作项。 在这种情况下,可指定 System.Threading.Tasks.TaskCreationOptions 选项,它提示附加线程执行任务时可能需要计划程序,以使此任务不阻塞本地队列中其他线程或工作项的向前推动。 使用此选项可以完全避免线程池,包括全局队列和本地队列。

任务内联

在某些情况下,等待时 Task ,可能会在执行等待操作的线程上同步执行。 这可以通过防止需要其他线程,而改用现有的线程来增强性能,否则会阻止这种线程。 为了防止因重新进入而导致的错误,仅当在相关线程的本地队列中找到等待目标时,才会发生任务内联。

指定同步上下文

可使用 TaskScheduler.FromCurrentSynchronizationContext 方法指定任务应计划在特定线程上运行。 在 Windows 窗体和 Windows Presentation Foundation 等框架中此操作非常有用,在此类框架中对用户界面对象的访问限制为只可访问在创建 UI 对象的同一线程上运行的代码。

以下示例使用 TaskScheduler.FromCurrentSynchronizationContext Windows Presentation Foundation (WPF) 应用中的方法在创建用户界面(UI)控件的同一线程上计划任务。 该示例创建从指定目录中随机选择的图像马赛克。 WPF 对象用于加载和调整图像的大小。 然后将原始像素传递给使用 For 循环将像素数据写入大型单字节数组的任务。 不需要同步,因为没有两个磁贴占用相同的数组元素。 磁贴也可以按任意顺序写入,因为它们的位置独立于任何其他磁贴进行计算。 然后,将大型数组传递到 UI 线程上运行的任务,其中像素数据加载到图像控件中。

该示例将数据移出 UI 线程,使用并行循环和 Task 对象对其进行修改,然后将其传回 UI 线程上运行的任务。 当必须使用任务并行库来执行 WPF API 不支持的操作或不够快的操作时,此方法非常有用。 在 WPF 中创建图像马赛克的另一种方法是使用 System.Windows.Controls.WrapPanel 控件并向其添加图像。 处理 WrapPanel 定位磁贴的工作。 但是,只能在 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

若要创建示例,请在 Visual Studio 中创建 WPF 应用程序项目并将其命名为WPF_CS1(对于 C# WPF 项目)或WPF_VB1(对于 Visual Basic WPF 项目)。 然后执行以下操作:

  1. 在设计视图中,将Image工具箱中的控件拖到设计图面的左上角。 在“属性”窗口的“名称”文本框中,将控件命名为“image”。

  2. Button控件从工具箱拖到应用程序窗口左下半部分。 在 XAML 视图中,将 Content 按钮的属性指定为“生成马赛克”,并将其属性指定 Width 为“100”。 Clickbutton_Click通过添加到Click="button_Click"元素,连接示例代码中定义的事件处理程序的事件<Button>。 在“属性”窗口的“名称”文本框中,将控件命名为“按钮”。

  3. 将MainWindow.xaml.cs或MainWindow.xaml.vb文件的整个内容替换为此示例中的代码。 对于 C# WPF 项目,请确保工作区的名称与项目名称匹配。

  4. 该示例从名为 C:\Users\Public\Pictures\Sample Pictures 的目录中读取 JPEG 图像。 创建目录并将某些映像放入其中,或更改路径以引用包含图像的其他目录。

此示例存在一些限制。 例如,仅支持每个像素的 32 位图像;其他格式的图像在调整大小操作期间被 BitmapImage 对象损坏。 此外,源图像必须都大于磁贴大小。 作为进一步练习,可以添加用于处理多个像素格式和文件大小的功能。