System.Threading.Tasks.TaskScheduler 클래스

이 문서에서는 이 API에 대한 참조 설명서에 대한 추가 설명서를 제공합니다.

클래스는 TaskScheduler 작업 스케줄러를 나타냅니다. 작업 스케줄러는 작업의 작업이 최종적으로 실행되도록 합니다.

기본 작업 스케줄러는 부하 분산, 최대 처리량에 대한 스레드 주입/사용 중지 및 전반적인 성능 향상을 위한 작업 도용을 제공합니다. 대부분의 시나리오에서는 충분해야 합니다.

클래스는 TaskScheduler 사용자 지정 가능한 모든 예약 논리의 확장 지점으로도 사용됩니다. 여기에는 실행을 위해 작업을 예약하는 방법 및 예약된 작업을 디버거에 노출하는 방법과 같은 메커니즘이 포함됩니다. 특별한 기능이 필요한 경우 사용자 지정 스케줄러를 만들고 특정 작업 또는 쿼리에 사용하도록 설정할 수 있습니다.

기본 작업 스케줄러 및 스레드 풀

작업 병렬 라이브러리 및 PLINQ의 기본 스케줄러는 클래스가 나타내는 ThreadPool .NET 스레드 풀을 사용하여 작업을 큐에 대기하고 실행합니다. 스레드 풀은 병렬 작업 및 쿼리가 자주 나타내는 세분화된 병렬 처리(수명이 짧은 작업 단위)를 효율적으로 지원하기 위해 형식에서 제공하는 Task 정보를 사용합니다.

전역 큐와 로컬 큐 비교

스레드 풀 전역 FIFO (선입 선출) 큐 각 애플리케이션 도메인의 스레드에 대 한 작업을 유지 합니다. 프로그램이 (또는ThreadPool.UnsafeQueueUserWorkItem) 메서드를 호출 ThreadPool.QueueUserWorkItem 할 때마다 작업이 이 공유 큐에 배치되고 결국 사용할 수 있는 다음 스레드로 큐에서 해제됩니다. .NET Framework 4부터 이 큐는 클래스와 유사한 잠금 없는 알고리즘을 ConcurrentQueue<T> 사용합니다. 이 잠금 없는 구현을 사용하면 스레드 풀이 작업 항목을 큐에 대기하고 큐를 해제하는 데 소요되는 시간이 줄어듭니다. 이 성능 혜택은 스레드 풀을 사용하는 모든 프로그램에서 사용할 수 있습니다.

다른 작업의 컨텍스트에서 생성되지 않은 태스크인 최상위 작업은 다른 작업 항목과 마찬가지로 전역 큐에 배치됩니다. 그러나 다른 작업의 컨텍스트에서 만들어진 중첩 또는 자식 작업은 매우 다르게 처리됩니다. 자식 또는 중첩된 작업은 부모 태스크가 실행 중인 스레드와 관련된 로컬 큐에 배치됩니다. 부모 작업은 최상위 작업일 수도 있으며 다른 작업의 자식일 수도 있습니다. 이 스레드가 더 많은 작업을 수행할 준비가 되면 먼저 로컬 큐를 찾습니다. 작업 항목이 대기 중인 경우 신속하게 액세스할 수 있습니다. 로컬 큐는 캐시 지역성을 유지하고 경합을 줄이기 위해 LIFO(마지막 선입선출 순서)로 액세스됩니다. 자식 작업 및 중첩된 작업에 대한 자세한 내용은 연결된 자식 및 분리된 자식 작업을 참조 하세요.

로컬 큐를 사용하면 전역 큐에 대한 부담을 줄일 뿐만 아니라 데이터 지역성을 활용할 수 있습니다. 로컬 큐의 작업 항목은 메모리에서 물리적으로 서로 가까이 있는 데이터 구조를 자주 참조합니다. 이러한 경우 데이터는 첫 번째 작업이 실행된 후 캐시에 이미 있으며 신속하게 액세스할 수 있습니다. PLINQ(병렬 LINQ)Parallel 클래스는 모두 중첩된 작업과 자식 작업을 광범위하게 사용하고 로컬 작업 큐를 사용하여 상당한 속도 향상을 달성합니다.

작업 도용

.NET Framework 4부터 스레드 풀에는 다른 스레드가 큐에서 여전히 작업하는 동안 유휴 상태인 스레드가 없는지 확인하는 데 도움이 되는 작업 도용 알고리즘도 있습니다. 스레드 풀 스레드가 더 많은 작업을 수행할 준비가 되면 먼저 로컬 큐의 헤드, 전역 큐, 다른 스레드의 로컬 큐를 살펴봅니다. 다른 스레드의 로컬 큐에서 작업 항목을 찾은 경우 먼저 추론을 적용하여 작업을 효율적으로 실행할 수 있는지 확인합니다. 가능하면 작업 항목을 꼬리에서 큐에서 제거합니다(FIFO 순서). 이렇게 하면 각 로컬 큐에 대한 경합이 줄어들고 데이터 지역성이 유지됩니다. 이 아키텍처는 스레드 풀 부하 분산이 이전 버전보다 더 효율적으로 작동하도록 도와줍니다.

장기 실행 작업

작업이 로컬 큐에 배치되지 않도록 명시적으로 방지할 수 있습니다. 예를 들어 특정 작업 항목이 비교적 오랜 시간 동안 실행되고 로컬 큐의 다른 모든 작업 항목을 차단할 수 있음을 알 수 있습니다. 이 경우 로컬 큐에서 다른 스레드 또는 작업 항목의 정방향 진행을 차단하지 않도록 작업에 추가 스레드가 필요할 수 있다는 힌트를 스케줄러에 제공하는 옵션을 지정할 System.Threading.Tasks.TaskCreationOptions 수 있습니다. 이 옵션을 사용하면 전역 및 로컬 큐를 포함하여 스레드 풀을 완전히 방지할 수 있습니다.

작업 인라인 처리

대기 중인 경우에 Task 대기 작업을 수행하는 스레드에서 동기적으로 실행될 수 있습니다. 이렇게 하면 추가 스레드가 필요하지 않게 하고, 그렇지 않으면 차단된 기존 스레드를 대신 사용하여 성능을 향상시킵니다. 재진입으로 인한 오류를 방지하기 위해 작업 인라인 처리는 대기 대상이 관련 스레드의 로컬 큐에 있는 경우에만 발생합니다.

동기화 컨텍스트 지정

이 메서드를 TaskScheduler.FromCurrentSynchronizationContext 사용하여 특정 스레드에서 작업을 실행하도록 예약하도록 지정할 수 있습니다. 이는 사용자 인터페이스 개체에 대한 액세스가 UI 개체가 만들어진 동일한 스레드에서 실행되는 코드로 제한되는 Windows Forms 및 Windows Presentation Foundation과 같은 프레임워크에서 유용합니다.

다음 예제에서는 WPF(Windows Presentation Foundation) 앱의 메서드를 사용하여 TaskScheduler.FromCurrentSynchronizationContext UI(사용자 인터페이스) 컨트롤을 만든 것과 동일한 스레드에서 작업을 예약합니다. 이 예제에서는 지정된 디렉터리에서 임의로 선택된 이미지의 모자이크 이미지를 만듭니다. WPF 개체는 이미지를 로드하고 크기를 조정하는 데 사용됩니다. 그런 다음 원시 픽셀은 루프를 사용하여 For 픽셀 데이터를 큰 싱글 바이트 배열에 쓰는 작업에 전달됩니다. 두 타일이 동일한 배열 요소를 차지하지 않으므로 동기화가 필요하지 않습니다. 타일의 위치는 다른 타일과 독립적으로 계산되므로 어떤 순서로든 타일을 작성할 수 있습니다. 그런 다음 큰 배열은 픽셀 데이터가 이미지 컨트롤에 로드되는 UI 스레드에서 실행되는 작업에 전달됩니다.

이 예제에서는 UI 스레드에서 데이터를 이동하고 병렬 루프 및 Task 개체를 사용하여 수정한 다음 UI 스레드에서 실행되는 작업에 다시 전달합니다. 이 방법은 작업 병렬 라이브러리를 사용하여 WPF API에서 지원되지 않거나 충분히 빠르지 않은 작업을 수행해야 하는 경우에 유용합니다. WPF에서 이미지 모자이크를 만드는 또 다른 방법은 컨트롤을 System.Windows.Controls.WrapPanel 사용하여 이미지를 추가하는 것입니다. WrapPanel 타일 위치 지정 작업을 처리합니다. 그러나 이 작업은 UI 스레드에서만 수행할 수 있습니다.

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WPF_CS1
{
    /// <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 = null;

        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;
            image.Width = largeImagePixelWidth;
            image.Height = largeImagePixelHeight;
        }

        private void button_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 "image".
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            // On the UI thread, put the bytes into a bitmap and
            // display it in the Image control.
            var t3 = tiledImage.ContinueWith((antecedent) =>
            {
                // 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
                    antecedent.Result,
                    largeImageStride);
                image.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 bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.UriSource = new Uri(filename);
            bitmapImage.DecodePixelHeight = tilePixelHeight;
            bitmapImage.DecodePixelWidth = tilePixelWidth;
            bitmapImage.EndInit();

            format = bitmapImage.Format;
            int size = (int)(bitmapImage.Height * bitmapImage.Width);
            int stride = (int)bitmapImage.Width * 4;
            byte[] dest = new byte[stride * tilePixelHeight];

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

Partial Public Class MainWindow : Inherits Window
    Dim fileCount As Integer
    Dim colCount As Integer
    Dim rowCount As Integer
    Dim tilePixelHeight As Integer
    Dim tilePixelWidth As Integer
    Dim largeImagePixelHeight As Integer
    Dim largeImagePixelWidth As Integer
    Dim largeImageStride As Integer
    Dim format As PixelFormat
    Dim palette As BitmapPalette = Nothing

    Public Sub New()
        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)
        Me.Width = largeImagePixelWidth + 40
        image.Width = largeImagePixelWidth
        image.Height = largeImagePixelHeight
    End Sub

    Private Sub button_Click(sender As Object, e As RoutedEventArgs) _
        Handles button.Click

        ' For best results use 1024 x 768 jpg files at 32bpp.
        Dim files() As String = System.IO.Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures\", "*.jpg")

        fileCount = files.Length
        Dim images(fileCount - 1) As Task(Of Byte())
        For i As Integer = 0 To fileCount - 1
            Dim x As Integer = i
            images(x) = Task.Factory.StartNew(Function() LoadImage(files(x)))
        Next

        ' When they have all been loaded, tile them into a single byte array.
        'var tiledImage = Task.Factory.ContinueWhenAll(
        '        images, (i) >= TileImages(i));

        '        Dim tiledImage As Task(Of Byte()) = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())) TileImages(i))
        Dim tiledImage = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())()) 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".
        Dim UISyncContext = TaskScheduler.FromCurrentSynchronizationContext()

        ' On the UI thread, put the bytes into a bitmap and
        ' display it in the Image control.
        Dim t3 = tiledImage.ContinueWith(Sub(antecedent)
                                             ' Get System DPI.
                                             Dim m As Matrix = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice
                                             Dim dpiX As Double = m.M11
                                             Dim dpiY As Double = m.M22

                                             ' Use the default palette in creating the bitmap.
                                             Dim bms As BitmapSource = BitmapSource.Create(largeImagePixelWidth,
                                                                                           largeImagePixelHeight,
                                             dpiX,
                                             dpiY,
                                             format,
                                             palette,
                                             antecedent.Result,
                                             largeImageStride)
                                             image.Source = bms
                                         End Sub, UISyncContext)
    End Sub

    Public Function LoadImage(filename As String) As Byte()
        ' 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.
        Dim bitmapImage As New BitmapImage()
        bitmapImage.BeginInit()
        bitmapImage.UriSource = New Uri(filename)
        bitmapImage.DecodePixelHeight = tilePixelHeight
        bitmapImage.DecodePixelWidth = tilePixelWidth
        bitmapImage.EndInit()

        format = bitmapImage.Format
        Dim size As Integer = CInt(bitmapImage.Height * bitmapImage.Width)
        Dim stride As Integer = CInt(bitmapImage.Width * 4)
        Dim dest(stride * tilePixelHeight - 1) As Byte

        bitmapImage.CopyPixels(dest, stride, 0)

        Return dest
    End Function

    Function Stride(pixelWidth As Integer, bitsPerPixel As Integer) As Integer
        Return (((pixelWidth * bitsPerPixel + 31) / 32) * 4)
    End Function

    ' 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.
    Function TileImages(sourceImages As Task(Of Byte())()) As Byte()
        Dim largeImage(largeImagePixelHeight * largeImageStride - 1) As Byte
        Dim tileImageStride As Integer = tilePixelWidth * 4 ' hard coded To 32bpp

        Dim rand As New Random()
        Parallel.For(0, rowCount * colCount, Sub(i)
                                                 ' Pick one of the images at random for this tile.
                                                 Dim cur As Integer = rand.Next(0, sourceImages.Length)
                                                 Dim pixels() As Byte = sourceImages(cur).Result

                                                 ' Get the starting index for this tile.
                                                 Dim row As Integer = i \ colCount
                                                 Dim col As Integer = i Mod colCount
                                                 Dim idx As Integer = ((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.
                                                 Dim tileImageIndex As Integer = 0
                                                 For j As Integer = 0 To tilePixelHeight - 1
                                                     ' Write the next scanline for this tile.
                                                     For k As Integer = 0 To tileImageStride - 1
                                                         largeImage(idx) = pixels(tileImageIndex)
                                                         idx += 1
                                                         tileImageIndex += 1
                                                     Next
                                                     ' Advance to the beginning of the next scanline.
                                                     idx += largeImageStride - tileImageStride
                                                 Next
                                             End Sub)
        Return largeImage
    End Function
End Class

예제를 만들려면 Visual Studio에서 WPF 애플리케이션 프로젝트를 만들고 WPF_CS1 이름을 (에 대 한는 C# WPF 프로젝트) 또는 WPF_VB1 (Visual Basic WPF 프로젝트를)에 대 한 합니다. 그런 다음 아래 작업을 수행합니다.

  1. 디자인 보기에서 도구 상자에서 디자인 화면의 왼쪽 위 모서리로 컨트롤을 끕 Image 니다. 속성 창의 이름 텍스트 상자에서 컨트롤 이름을 "image"로 지정합니다.

  2. 끌어서를 Button 에서 제어 합니다 도구 상자 애플리케이션 창의 왼쪽된 아래 부분에 합니다. XAML 보기에서 단추의 속성을 "모자이크 만들기"로 지정 Content 하고 해당 Width 속성을 "100"으로 지정합니다. 요소에 Click 추가하여 Click="button_Click"<Button> 예제 코드에 정의된 이벤트 처리기를 사용하여 이벤트를 button_Click 커넥트. 속성 창의 이름 텍스트 상자에서 컨트롤 이름을 "button"으로 지정합니다.

  3. MainWindow.xaml.cs 또는 MainWindow.xaml.vb 파일의 전체 내용을 이 예제의 코드로 바꿉니다. C# WPF 프로젝트의 경우 작업 영역의 이름이 프로젝트 이름과 일치하는지 확인합니다.

  4. 이 예제에서는 C:\Users\Public\Pictures\Sample Pictures라는 디렉터리에서 JPEG 이미지를 읽습니다. 디렉터리를 만들고 일부 이미지를 배치하거나, 이미지가 포함된 다른 디렉터리를 참조하도록 경로를 변경합니다.

이 예제에는 몇 가지 제한 사항이 있습니다. 예를 들어 픽셀당 32비트 이미지만 지원됩니다. 다른 형식의 이미지는 크기 조정 작업 중에 개체에 의해 BitmapImage 손상됩니다. 또한 원본 이미지는 모두 타일 크기보다 커야 합니다. 추가 연습에서는 여러 픽셀 형식 및 파일 크기를 처리하는 기능을 추가할 수 있습니다.