Como: Agenda de trabalho em um contexto de sincronização especificado

Este exemplo mostra como usar o TaskScheduler.FromCurrentSynchronizationContext método em um aplicativo de Windows Presentation Foundation (WPF) para agendar uma tarefa no mesmo thread que o controle de interface (UI) do usuário foi criado no.

Procedimentos

Para criar o projeto do WPF

  1. Na Visual Studio, crie um projeto de aplicativo do WPF e nomeá-la.

  2. No modo de design, arraste um controle de imagem a partir de Toolbox para a superfície de design. No modo de exibição XAML, especifique o alinhamento horizontal como "Left". O tamanho não importa porque o controle será redimensionado dinamicamente em tempo de execução. Aceite o nome padrão, "image1".

  3. Arraste um botão a partir de Toolbox para a parte inferior esquerda da janela do aplicativo. Clique duas vezes no botão para adicionar um manipulador de eventos Click. No modo de exibição XAML, especifique o Content propriedade do botão como "fazer um mosaico" e especifique o alinhamento horizontal como "Left".

  4. No arquivo MainWindow.xaml.cs, use o seguinte código para substituir todo o conteúdo do arquivo. Certifique-se de que o nome do namespace corresponde ao nome do projeto.

  5. Pressione F5 para executar o aplicativo. Sempre que você clicar no botão, uma nova organização peças deve ser exibida.

Exemplo

Descrição

O exemplo a seguir cria um mosaico de imagens que são seleccionados aleatoriamente de um diretório especificado. Os objetos do WPF são usados para carregar e redimensionar as imagens. Em seguida, os pixels brutos são passados para uma tarefa que usa um ParallelFor() loop para gravar os dados de pixel em uma matriz de byte único grande. Nenhuma sincronização é necessária porque não há duas peças ocupam os mesmos elementos de matriz. Os ladrilhos também podem ser escritos em qualquer ordem, porque sua posição é calculada independentemente de qualquer outra lado a lado. Grande array é então passado para uma tarefa executada no thread da interface do usuário, onde os dados de pixel são carregados em um controle de imagem.

Código

using System;
using System.Collections.Generic;

using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace wpfApplication1
{
    /// <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;

        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;
            image1.Width = largeImagePixelWidth;
            image1.Height = largeImagePixelHeight;


        }

        private void button1_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 "image1".
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            //  On the UI thread, put the bytes into a bitmap and
            // and display it in the Image control.
            var t3 = tiledImage.ContinueWith((antedecent) =>
            {
                // 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
                    antedecent.Result,
                    largeImageStride);
                image1.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 myBitmapImage = new BitmapImage();
            myBitmapImage.BeginInit();
            myBitmapImage.UriSource = new Uri(filename);
            tilePixelHeight = myBitmapImage.DecodePixelHeight = tilePixelHeight;
            tilePixelWidth = myBitmapImage.DecodePixelWidth = tilePixelWidth;
            myBitmapImage.EndInit();

            format = myBitmapImage.Format;
            int size = (int)(myBitmapImage.Height * myBitmapImage.Width);
            int stride = (int)myBitmapImage.Width * 4;
            byte[] dest = new byte[stride * tilePixelHeight];

            myBitmapImage.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;
        }
    }
}

Comentários

Este exemplo demonstra como mover os dados fora do segmento de UI, modificá-lo por meio de loops paralelos e objetos de tarefa e, em seguida, passá-lo 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 estão suficientemente rápido. Outra maneira de criar um mosaico de imagem no WPF é usar um WrapPanel de objeto e adicionar imagens ao proprietário. O WrapPanel manipulará o trabalho de posicionamento lado a lado. No entanto, esse trabalho só pode ser executado no thread da interface do usuário.

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

Consulte também

Conceitos

Agendadores de tarefa

Advanced Topics (Task Parallel Library)