Поделиться через


Практическое руководство. Планирование работы в указанном контексте синхронизации

В этом примере показано, как использовать метод TaskScheduler.FromCurrentSynchronizationContext в приложении Windows Presentation Foundation (WPF) для планирования задач в одном потоке, который был создан элементом управления пользовательского интерфейса.

Процедуры

Создание проекта WPF

  1. Создайте проект приложения WPF в среде Visual Studio и дайте ему имя.

  2. В представлении конструирования перетащите элемент управления Image из Панели элементов в рабочую область конструирования. В представлении XAML задайте горизонтальное выравнивание — по левому краю (Left). Размер не имеет значения — элемент управления будет динамически изменять размер во время выполнения. Примите имя по умолчанию, image1.

  3. Перетащите кнопку с Панели элементов в левую нижнюю часть окна приложения. Дважды щелкните кнопку для добавления обработчика событий Click. В представлении XAML установите для свойства Content кнопки значение Make a Mosaic и задайте значение горизонтального выравнивания как Left.

  4. В файле MainWindow.xaml.cs замените содержимое всего файла следующим кодом. Убедитесь, что имя пространства имен совпадает с именем проекта.

  5. Нажмите клавишу F5 для запуска приложения. Каждый раз при нажатии на кнопку должно отображаться новое расположение элементов мозаики.

Пример

Описание

В следующем примере создается мозаика из изображений, которые выбираются случайным образом из заданного каталога. Для загрузки и изменения размеров изображения используются объекты WPF. Затем необработанные пиксели передаются задаче, которая использует цикл ParallelFor() для записи пиксельных данных в большой однобайтовый массив. Синхронизация не требуется, так как поскольку два элемента мозаики не могут занимать одинаковые элементы массива. Элементы мозаики также могут быть записаны в любом порядке, так как их расположение рассчитывается независимо друг от друга. Затем большой массив передается в задачу, выполняющуюся в потоке пользовательского интерфейса, и пиксельных данные загружаются в элемент управления Image.

Код

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

Комментарии

Этот пример показывает как извлечь данных из потока пользовательского интерфейса, изменить их с использованием параллельных циклов и объектов Task, затем передать их назад в задачу, которая выполняется в потоке пользовательского интерфейса. Данный подход рекомендуется, если необходимо использовать библиотеку параллельных задач для выполнения операций, которые не поддерживаются API WPF или недостаточно быстрые. Другой способ создания мозаики из изображения в WPF это использование объекта WrapPanel и добавление изображений в него. Объект WrapPanel будет выполнять работу по расположению элементов мозаики. Однако такая работа может быть выполнена только в потоке пользовательского интерфейса.

Данный пример содержит определенные ограничения. Например, поддерживаются только изображения с 32 битами на пиксель; изображения в других форматах повреждаются объектом BitmapImage при выполнении операции изменения размера. Также исходные изображения должны все быть больше, чем размер элемента мозаики. В качестве дополнительного упражнения можно добавить возможности по обработке различных форматов пикселей и размеров файлов.

См. также

Основные понятия

Планировщики заданий

Advanced Topics (Task Parallel Library)