Cómo: Programar el trabajo en un contexto de sincronización especificado
En este ejemplo se muestra cómo utilizar el método TaskScheduler.FromCurrentSynchronizationContext en una aplicación Windows Presentation Foundation (WPF) para programar una tarea en el mismo subproceso en que se creó el control de interfaz de usuario.
Procedimientos
Para crear el proyecto de WPF
En Visual Studio, cree un proyecto de aplicación WPF y asígnele un nombre.
En la vista de diseño, arrastre un control Image del Cuadro de herramientas a la superficie de diseño. En la vista de XAML, especifique la alineación horizontal como "Left". El tamaño no importa porque el tamaño del control se cambiará dinámicamente en tiempo de ejecución. Acepte el nombre predeterminado "image1".
Arrastre un botón del Cuadro de herramientas hacia la parte inferior izquierda de la ventana de la aplicación. Haga doble clic en el botón para agregar el controlador predeterminado del evento Click. En la vista XAML, especifique la propiedad Content del botón como "Make a Mosaic" y especifique la alineación horizontal en "Left".
En el archivo MainWindow.xaml.cs, utilice el siguiente código para reemplazar el contenido completo del archivo. Asegúrese de que el nombre del espacio de nombres coincide con el nombre del proyecto.
Presione F5 para ejecutar la aplicación. Cada vez que se hace clic en el botón, se debería mostrar una nueva organización de mosaicos.
Ejemplo
Descripción
En el siguiente ejemplo se crea un mosaico de imágenes que se seleccionan de forma aleatoria de un directorio especificado. Los objetos WPF se utilizan para cargar y cambiar el tamaño de las imágenes. A continuación, los píxeles sin formato se pasan a una tarea que utiliza un bucle ParallelFor() para escribir los datos de píxeles en una matriz grande de un solo byte. No se requiere ninguna sincronización porque ninguno de los dos mosaicos ocupa los mismos elementos de la matriz. Los mosaicos también se pueden escribir en cualquier orden porque su posición se calcula independientemente de cualquier otro mosaico. A continuación, la matriz grande se pasa a una tarea que se ejecuta en el subproceso de la interfaz de usuario, donde los datos de píxeles se cargan en un control Image.
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;
}
}
}
Comentarios
En este ejemplo se muestra cómo mover los datos del subproceso de la interfaz de usuario, modificarlos usando bucles paralelos y objetos Task y, a continuación, devolverlos a una tarea que se ejecuta en el subproceso de la interfaz de usuario. Este enfoque es útil cuando se tiene que utilizar Task Parallel Library para realizar operaciones admitidas o no admitidas por la API de WPF, o que no son suficientemente rápidas. Otra manera de crear un mosaico de la imagen en WPF es utilizar un objeto WrapPanel y agregarle las imágenes. WrapPanel controlará el trabajo de colocar los mosaicos. Sin embargo, este trabajo solo se puede realizar en el subproceso de la interfaz de usuario.
Este ejemplo tiene algunas limitaciones. Solo se admiten imágenes de 32 bits por píxel, por ejemplo; el objeto BitmapImage daña las imágenes de otros formatos durante la operación de cambio de tamaño. Además las imágenes de origen deben ser mayores que el tamaño del mosaico. Como un ejercicio más extenso, puede agregar la funcionalidad para controlar varios formatos de píxel y tamaños de archivo.