HOW TO:排程在特定的同步處理內容上運作
這個範例顯示如何在 Windows Presentation Foundation (WPF) 應用程式中使用 TaskScheduler.FromCurrentSynchronizationContext 方法,以在建立使用者介面 (UI) 控制項的同一個執行緒上排定工作。
程序
若要建立 WPF 專案
在 Visual Studio 中,建立 WPF 應用程式專案並加以命名。
在設計檢視中,將 Image 控制項從 [工具箱] 拖曳至設計介面。 在 XAML 檢視中,將水平對齊方式指定為 [左]。大小則無所謂,因為控制項會在執行階段動態調整大小。 接受預設名稱 "image1"。
將按鈕從 [工具箱] 拖曳至應用程式視窗的左下方部分。 按兩下按鈕以加入 Click 事件處理常式。 在 XAML 檢視中,將按鈕的 Content 屬性指定為 [製作馬賽克],並將水平對齊方式指定為 [左]。
在 MainWindow.xaml.cs 檔案中,使用下列程式碼來取代整個檔案的內容。 請確定命名空間的名稱與專案名稱相符。
請按 F5 執行應用程式。 每次您按一下按鈕時,應會顯示新的方磚排列組合。
範例
說明
下列程式碼範例會建立從指定目錄隨機選取之影像的馬賽克。 首先,會使用 WPF 物件載入影像並調整影像大小。 接著,會將原始像素傳遞給某項工作,這項工作會使用 ParallelFor() 迴圈將像素資料寫入至大型的單一位元組陣列。 沒有任兩個方磚會佔用同一個陣列元素,所以不需要同步處理。 另外,方磚的寫入順序也沒有影響,因為計算方磚位置時並不會將任何其他方磚的位置納入考慮。 然後,便會將這個大型陣列傳遞給在 UI 執行緒上執行的工作,像素資料會在該處載入至 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;
}
}
}
註解
這個範例示範如何將資料移出 UI 執行緒、使用平行迴圈修改資料,然後將資料傳回在 UI 執行緒上執行的工作。 當您必須使用工作平行程式庫來執行 WPF API 不支援的作業或是速度太慢的作業時,這個方法很有用。 另一個在 WPF 中建立影像馬賽克的方法,就是使用 WrapPanel 物件並將影像加入其中。 WrapPanel 會負責處理方磚的定位工作。 但是,這個工作只能在 UI 執行緒上執行。
這個範例有一些限制。 例如,只支援 32 bpp 影像,其他格式的影像會在調整大小作業期間被 BitmapImage 物件損毀。 另外,來源影像全都必須大於方磚大小。 您可以加入處理多個像素格式和檔案大小的功能,以做為進階練習。