Gewusst wie: Planen von Arbeiten an einem angegebenen Synchronisierungskontext

In diesem Beispiel wird gezeigt, wie die TaskScheduler.FromCurrentSynchronizationContext-Methode in einer Windows Presentation Foundation (WPF)-Anwendung verwendet wird, um in dem Thread, in dem das Benutzeroberflächen-Steuerelement erstellt wurde, einen Task zu planen.

Verfahren

So erstellen Sie das WPF-Projekt

  1. Erstellen Sie in Visual Studio ein WPF-Anwendungsprojekt, und weisen Sie ihm einen Namen zu.

  2. Ziehen Sie in der Entwurfsansicht ein Bildsteuerelement aus der Toolbox auf die Entwurfsoberfläche. Geben Sie in der XAML-Ansicht als horizontale Ausrichtung "Links" an. Die Größe spielt keine Rolle, da die Größe des Steuerelements zur Laufzeit dynamisch geändert wird. Übernehmen Sie den Standardnamen ("Bild1").

  3. Ziehen Sie eine Schaltfläche von der Toolbox in den linken unteren Teil des Anwendungsfensters. Doppelklicken Sie auf die Schaltfläche, um einen Click-Ereignishandler hinzuzufügen. Geben Sie in der XAML-Ansicht die Content-Eigenschaft der Schaltfläche als "Mosaik erstellen", und geben Sie als horizontale Ausrichtung "Links" an.

  4. Verwenden Sie in der Datei "MainWindow.xaml.cs" den folgenden Code, um den gesamten Inhalt der Datei zu ersetzen. Stellen Sie sicher, dass der Name des Namespaces mit dem Projektnamen übereinstimmt.

  5. Drücken Sie F5, um die Anwendung auszuführen. Beim Klicken auf die Schaltfläche wird eine neue Anordnung von Kacheln angezeigt.

Beispiel

Beschreibung

Im folgenden Beispiel wird ein Mosaik von Bildern erstellt, die zufällig aus einem angegebenen Verzeichnis ausgewählt werden. Die WPF-Objekte werden verwendet, um die Bilder zu laden und ihre Größe zu ändern. Anschließend werden die unformatierten Pixel an einen Task übergeben, für den eine ParallelFor()-Schleife verwendet wird, um die Pixeldaten in ein großes Einzelbytearray zu schreiben. Es ist keine Synchronisierung erforderlich, da die gleichen Arrayelemente nicht von zwei Kacheln belegt werden. Die Kacheln können auch in jeder beliebigen Reihenfolge geschrieben werden, da ihre Position unabhängig von anderen Kacheln berechnet wird. Das große Array wird anschließend an eine Aufgabe übergeben, die in dem UI-Thread ausgeführt wird, wo die Pixeldaten in ein Bildsteuerelement geladen werden.

Code

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

Kommentare

In diesem Beispiel wird veranschaulicht, wie Daten vom UI-Thread verschoben, mit parallelen Schleifen und Taskobjekten geändert und anschließend wieder an den Task übergeben werden, der im UI-Thread ausgeführt wird. Diese Methode ist hilfreich, wenn mit der Task Parallel Library Vorgänge ausgeführt werden müssen, die entweder nicht von der WPF-API unterstützt werden oder nicht schnell genug sind. Eine andere Möglichkeit, in WPF ein Bildmosaik zu erstellen, ist die Verwendung eines WrapPanel-Objekts und das Hinzufügen von Bildern zum Objekt. WrapPanel übernimmt die Positionierung der Kacheln. Dieser Schritt kann jedoch nur im UI-Thread ausgeführt werden.

Für dieses Beispiel gelten einige Einschränkungen. Zum Beispiel werden nur Bilder mit 32 Bit pro Pixel unterstützt; Bilder in anderen Formaten werden während der Größenänderung vom BitmapImage-Objekt beschädigt. Außerdem müssen die Quellbilder alle größer als die Kacheln sein. Als weitere Übung können Sie die Funktion hinzufügen, um mehrere Pixelformate und Dateigrößen zu behandeln.

Siehe auch

Konzepte

Taskplaner

Erweiterte Themen (Task Parallel Library)