共用方式為


HOW TO:排程在特定的同步處理內容上運作

這個範例顯示如何在 Windows Presentation Foundation (WPF) 應用程式中使用 TaskScheduler.FromCurrentSynchronizationContext 方法,以在建立使用者介面 (UI) 控制項的同一個執行緒上排定工作。

程序

若要建立 WPF 專案

  1. 在 Visual Studio 中,建立 WPF 應用程式專案並加以命名。

  2. 在設計檢視中,將 Image 控制項從 [工具箱] 拖曳至設計介面。 在 XAML 檢視中,將水平對齊方式指定為 [左]。大小則無所謂,因為控制項會在執行階段動態調整大小。 接受預設名稱 "image1"。

  3. 將按鈕從 [工具箱] 拖曳至應用程式視窗的左下方部分。 按兩下按鈕以加入 Click 事件處理常式。 在 XAML 檢視中,將按鈕的 Content 屬性指定為 [製作馬賽克],並將水平對齊方式指定為 [左]。

  4. 在 MainWindow.xaml.cs 檔案中,使用下列程式碼來取代整個檔案的內容。 請確定命名空間的名稱與專案名稱相符。

  5. 請按 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 物件損毀。 另外,來源影像全都必須大於方磚大小。 您可以加入處理多個像素格式和檔案大小的功能,以做為進階練習。

請參閱

概念

工作排程器

Advanced Topics (Task Parallel Library)