Compartilhar via


System.Threading.Tasks.TaskScheduler classe

Este artigo fornece observações complementares à documentação de referência para essa API.

A TaskScheduler classe representa um agendador de tarefas. Um agendador de tarefas garante que o trabalho de uma tarefa seja eventualmente executado.

O agendador de tarefas padrão fornece roubo de trabalho para balanceamento de carga, injeção/desativação de thread para taxa de transferência máxima e bom desempenho geral. Deve ser suficiente para a maioria dos cenários.

A TaskScheduler classe também serve como ponto de extensão para toda a lógica de agendamento personalizável. Isso inclui mecanismos como agendar uma tarefa para execução e como as tarefas agendadas devem ser expostas aos depuradores. Se você precisar de uma funcionalidade especial, poderá criar um agendador personalizado e habilitá-lo para tarefas ou consultas específicas.

O agendador de tarefas padrão e o pool de threads

O agendador padrão para a Biblioteca Paralela de Tarefas e PLINQ usa o pool de threads .NET, que é representado pela classe, para enfileirar e executar o ThreadPool trabalho. O pool de threads usa as informações fornecidas pelo Task tipo para oferecer suporte eficiente ao paralelismo refinado (unidades de trabalho de curta duração) que tarefas e consultas paralelas geralmente representam.

A fila global versus as filas locais

O pool de threads mantém uma fila de trabalho FIFO global (primeiro a entrar, primeiro a sair) para threads em cada domínio de aplicativo. Sempre que um programa chama o método (ou ThreadPool.UnsafeQueueUserWorkItem), o trabalho é colocado nessa fila compartilhada e, eventualmente, removido da fila para o ThreadPool.QueueUserWorkItem próximo thread que fica disponível. A partir do .NET Framework 4, essa fila usa um algoritmo sem bloqueio semelhante à ConcurrentQueue<T> classe. Usando essa implementação sem bloqueio, o pool de threads gasta menos tempo quando enfileira e elimina itens de trabalho. Esse benefício de desempenho está disponível para todos os programas que usam o pool de threads.

As tarefas de nível superior, que são tarefas que não são criadas no contexto de outra tarefa, são colocadas na fila global como qualquer outro item de trabalho. No entanto, tarefas aninhadas ou filhas, que são criadas no contexto de outra tarefa, são tratadas de forma bastante diferente. Uma tarefa filha ou aninhada é colocada em uma fila local específica para o thread no qual a tarefa pai está sendo executada. A tarefa pai pode ser uma tarefa de nível superior ou também pode ser filha de outra tarefa. Quando esse thread estiver pronto para mais trabalho, ele primeiro será exibido na fila local. Se os itens de trabalho estiverem aguardando, eles podem ser acessados rapidamente. As filas locais são acessadas em ordem de entrada e primeira saída (LIFO) para preservar a localidade do cache e reduzir a contenção. Para obter mais informações sobre tarefas filho e tarefas aninhadas, consulte Tarefas filhas anexadas e separadas.

O uso de filas locais não apenas reduz a pressão sobre a fila global, mas também aproveita a localidade dos dados. Os itens de trabalho na fila local frequentemente fazem referência a estruturas de dados que estão fisicamente próximas umas das outras na memória. Nesses casos, os dados já estão no cache após a execução da primeira tarefa e podem ser acessados rapidamente. Tanto o LINQ paralelo (PLINQ) quanto a Parallel classe usam tarefas aninhadas e tarefas filho extensivamente e alcançam acelerações significativas usando as filas de trabalho locais.

Roubo de obras

A partir do .NET Framework 4, o pool de threads também apresenta um algoritmo de roubo de trabalho para ajudar a garantir que nenhum thread fique ocioso enquanto outros ainda têm trabalho em suas filas. Quando um thread de pool de threads está pronto para mais trabalho, ele primeiro examina o cabeçalho de sua fila local, depois na fila global e, em seguida, nas filas locais de outros threads. Se ele encontrar um item de trabalho na fila local de outro thread, ele primeiro aplicará heurísticas para garantir que ele possa executar o trabalho de forma eficiente. Se puder, ele elimina o item de trabalho da cauda (na ordem FIFO). Isso reduz a contenção em cada fila local e preserva a localidade dos dados. Essa arquitetura ajuda o balanceamento de carga do pool de threads a trabalhar com mais eficiência do que as versões anteriores.

Tarefas de longa duração

Talvez você queira impedir explicitamente que uma tarefa seja colocada em uma fila local. Por exemplo, você pode saber que um determinado item de trabalho será executado por um tempo relativamente longo e provavelmente bloqueará todos os outros itens de trabalho na fila local. Nesse caso, você pode especificar a opção, que fornece uma dica ao agendador de que um thread adicional pode ser necessário para a System.Threading.Tasks.TaskCreationOptions tarefa para que ele não bloqueie o progresso de encaminhamento de outros threads ou itens de trabalho na fila local. Usando essa opção, você evita completamente o pool de threads, incluindo as filas globais e locais.

Tarefa inlining

Em alguns casos, quando um Task é aguardado, ele pode ser executado de forma síncrona no thread que está executando a operação de espera. Isso melhora o desempenho, evitando a necessidade de um thread adicional e, em vez disso, usando o thread existente, que teria bloqueado de outra forma. Para evitar erros devido à reentrância, o preenchimento de tarefas ocorre somente quando o destino de espera é encontrado na fila local do thread relevante.

Especificar um contexto de sincronização

Você pode usar o TaskScheduler.FromCurrentSynchronizationContext método para especificar que uma tarefa deve ser agendada para ser executada em um thread específico. Isso é útil em estruturas como Windows Forms e Windows Presentation Foundation, em que o acesso a objetos de interface do usuário geralmente é restrito ao código que está sendo executado no mesmo thread no qual o objeto de interface do usuário foi criado.

O exemplo a seguir usa o método em um aplicativo Windows Presentation Foundation (WPF) para agendar uma tarefa no mesmo thread em que o TaskScheduler.FromCurrentSynchronizationContext controle de interface do usuário (UI) foi criado. O exemplo cria um mosaico de imagens que são selecionadas aleatoriamente de um diretório especificado. Os objetos WPF são usados para carregar e redimensionar as imagens. Os pixels brutos são então passados para uma tarefa que usa um For loop para gravar os dados de pixel em uma grande matriz de byte único. Nenhuma sincronização é necessária porque não há dois blocos ocupando os mesmos elementos de matriz. Os blocos também podem ser escritos em qualquer ordem, pois sua posição é calculada independentemente de qualquer outro bloco. A matriz grande é então passada para uma tarefa que é executada no thread da interface do usuário, onde os dados de pixel são carregados em um controle Image.

O exemplo move dados para fora do thread da interface do usuário, modifica-os usando loops e Task objetos paralelos e, em seguida, os passa de volta para uma tarefa executada no thread da interface do usuário. Essa abordagem é útil quando você precisa usar a Biblioteca Paralela de Tarefas para executar operações que não são suportadas pela API do WPF ou não são suficientemente rápidas. Outra maneira de criar um mosaico de imagens no WPF é usar um System.Windows.Controls.WrapPanel controle e adicionar imagens a ele. O WrapPanel trabalho de posicionamento das telhas é feito com alças. No entanto, esse trabalho só pode ser executado no thread da interface do usuário.

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

Para criar o exemplo, crie um projeto de aplicativo WPF no Visual Studio e nomeie-o WPF_CS1 (para um projeto WPF C#) ou WPF_VB1 (para um projeto WPF do Visual Basic). Em seguida, faça isso:

  1. No modo de design, arraste um Image controle da Caixa de Ferramentas para o canto superior esquerdo da superfície de design. Na caixa de texto Nome da janela Propriedades, nomeie o controle como "imagem".

  2. Arraste um Button controle da Caixa de Ferramentas para a parte inferior esquerda da janela do aplicativo. No modo de exibição XAML, especifique a Content propriedade do botão como "Criar um mosaico" e especifique sua Width propriedade como "100". Conecte o evento com o Clickbutton_Click manipulador de eventos definido no código do exemplo adicionando Click="button_Click" ao <Button> elemento . Na caixa de texto Nome da janela Propriedades, nomeie o controle como "botão".

  3. Substitua todo o conteúdo do arquivo MainWindow.xaml.cs ou MainWindow.xaml.vb pelo código deste exemplo. Para um projeto WPF C#, verifique se o nome do espaço de trabalho corresponde ao nome do projeto.

  4. O exemplo lê imagens JPEG de um diretório chamado C:\Users\Public\Pictures\Sample Pictures. Crie o diretório e coloque algumas imagens nele, ou altere o caminho para se referir a algum outro diretório que contenha imagens.

Este exemplo tem algumas limitações. Por exemplo, apenas imagens de 32 bits por pixel são suportadas; Imagens em outros formatos são corrompidas BitmapImage pelo objeto durante a operação de redimensionamento. Além disso, as imagens de origem devem ser todas maiores do que o tamanho do bloco. Como um exercício adicional, você pode adicionar funcionalidade para lidar com vários formatos de pixel e tamanhos de arquivo.