다음을 통해 공유


방법: 지정된 동기화 컨텍스트에 대한 작업 예약

이 예제에서는 WPF(Windows Presentation Foundation) 응용 프로그램에서 TaskScheduler.FromCurrentSynchronizationContext 메서드를 사용하여 UI(사용자 인터페이스) 컨트롤이 만들어진 동일한 스레드에 대해 작업을 예약하는 방법을 보여 줍니다.

절차

WPF 프로젝트를 만들려면

  1. Visual Studio에서 WPF 응용 프로그램 프로젝트를 만들고 이름을 지정합니다.

  2. 디자인 뷰에서 도구 상자의 이미지 컨트롤을 디자인 화면으로 끌어 옵니다. XAML 뷰에서 가로 맞춤을 "왼쪽"으로 지정합니다. 컨트롤의 크기는 런타임에 동적으로 조정되므로 크기에는 신경 쓰지 않아도 됩니다. 기본 이름 "image1"을 적용합니다.

  3. 도구 상자에서 단추를 응용 프로그램의 왼쪽 아래 부분으로 끌어 옵니다. 단추를 두 번 클릭하여 클릭 이벤트 처리기를 추가합니다. XAML 뷰에서 단추의 Content 속성을 "Make a Mosaic"으로 지정하고 가로 맞춤을 "왼쪽"으로 지정합니다.

  4. MainWindow.xaml.cs 파일에서 다음 코드를 사용하여 파일의 전체 내용을 바꿉니다. 네임스페이스 이름이 프로젝트 이름과 일치하는지 확인합니다.

  5. F5 키를 눌러 응용 프로그램을 실행합니다. 단추를 클릭할 때마다 타일이 새롭게 정렬되어 표시되어야 합니다.

예제

설명

다음 예제에서는 지정된 디렉터리에서 임의로 선택되는 이미지 모자이크를 만듭니다. WPF 개체는 이미지를 로드하고 이미지 크기를 조정하는 데 사용됩니다. 그런 다음 픽셀 데이터를 큰 싱글바이트 배열에 쓰기 위해 ParallelFor() 루프를 사용하는 작업으로 원시 픽셀이 전달됩니다. 두 타일이 동일한 배열 요소를 차지하지는 않기 때문에 동기화는 필요하지 않습니다. 또한 타일 위치는 다른 타일과 관계없이 계산되므로 타일은 원하는 순서대로 기록될 수 있습니다. 그런 다음 UI 스레드에서 실행되는 작업으로 큰 배열이 전달되고 이 스레드에서 픽셀 데이터가 이미지 컨트롤로 로드됩니다.

코드

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에서 지원되지 않거나 속도가 충분치 않은 작업을 수행해야 하는 경우에 유용합니다. 또는 WrapPanel 개체를 사용하고 이미지를 이 개체에 추가하여 WPF에서 이미지 모자이크를 만들 수도 있습니다. WrapPanel에서는 타일 위치를 지정하는 작업을 처리합니다. 그러나 이 작업은 UI 스레드에서만 수행될 수 있습니다.

이 예제에는 일부 제한 사항이 있습니다. 예를 들어, 픽셀당 32비트 이미지는 지원되지만 다른 형식의 이미지는 크기 조정 작업 중에 BitmapImage 개체에 의해 손상됩니다. 또한 모든 소스 이미지는 타일보다 커야 합니다. 심화 연습 과정으로 여러 픽셀 형식 및 파일 크기를 처리하는 기능을 추가해 볼 수 있습니다.

참고 항목

개념

작업 스케줄러

Advanced Topics (Task Parallel Library)