Практическое руководство. Планирование работы в указанном контексте синхронизации
В этом примере показано, как использовать метод TaskScheduler.FromCurrentSynchronizationContext в приложении Windows Presentation Foundation (WPF) для планирования задач в одном потоке, который был создан элементом управления пользовательского интерфейса.
Процедуры
Создание проекта WPF
Создайте проект приложения WPF в среде Visual Studio и дайте ему имя.
В представлении конструирования перетащите элемент управления Image из Панели элементов в рабочую область конструирования. В представлении XAML задайте горизонтальное выравнивание — по левому краю (Left). Размер не имеет значения — элемент управления будет динамически изменять размер во время выполнения. Примите имя по умолчанию, image1.
Перетащите кнопку с Панели элементов в левую нижнюю часть окна приложения. Дважды щелкните кнопку для добавления обработчика событий Click. В представлении XAML установите для свойства Content кнопки значение Make a Mosaic и задайте значение горизонтального выравнивания как Left.
В файле MainWindow.xaml.cs замените содержимое всего файла следующим кодом. Убедитесь, что имя пространства имен совпадает с именем проекта.
Нажмите клавишу 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 при выполнении операции изменения размера. Также исходные изображения должны все быть больше, чем размер элемента мозаики. В качестве дополнительного упражнения можно добавить возможности по обработке различных форматов пикселей и размеров файлов.