共用方式為


System.Threading.Tasks.TaskScheduler 類別

本文提供此 API 參考文件的補充備註。

類別 TaskScheduler 代表工作排程器。 工作排程器可確保最終會執行工作 (Task) 的工作 (Work)。

默認工作排程器會針對負載平衡、線程插入/淘汰的最大輸送量,以及整體良好的效能提供工作竊取。 這應該足以應付大多數的情況。

類別 TaskScheduler 也會作為所有可自定義排程邏輯的延伸點。 這包括一些機制,例如如何排程工作以供執行,以及排程工作應如何公開給調試程式。 如果您需要特殊功能,您可以建立自定義排程器,並針對特定工作或查詢加以啟用。

預設工作排程器和線程集區

工作平行連結庫和 PLINQ 的預設排程器會使用 類別所 ThreadPool 代表的 .NET 線程集區來排入佇列和執行工作。 線程集區會使用 型別所提供的 Task 資訊,有效率地支援平行工作和查詢通常代表的精細平行處理原則(短期工作單位)。

全域佇列與本機佇列

線程集區會針對每個應用程式域中的線程維護全域 FIFO(先出先出)工作佇列。 每當程式呼叫 ThreadPool.QueueUserWorkItem (或 ThreadPool.UnsafeQueueUserWorkItem) 方法時,工作就會放在這個共用佇列上,最後排入佇列到下一個可供使用的線程。 從 .NET Framework 4 開始,此佇列會使用類似 類別的 ConcurrentQueue<T> 無鎖定演算法。 藉由使用此無鎖定實作,線程集區會在排入佇列和取消佇列工作項目時花費較少的時間。 此效能優點可供使用線程集區的所有程式使用。

如同任何其他的工作 (Work) 項目,最上層工作 (Task) (也就是不是在其他工作 (Task) 的內容中建立的工作 (Task)) 會放入全域佇列中。 不過,巢狀工作或子工作 (也就是在其他工作的內容中建立的工作) 的處理方式則相當不同。 子工作或巢狀工作會放入執行父工作的執行緒專屬的本機佇列中。 父工作可以是最上層工作,也可以是其他工作的子工作。 這個執行緒在準備好要處理更多工作時,會先查看本機佇列。 如果本機佇列中有待處理的工作項目,則可以快速存取這些工作項目。 本機佇列會以最後先出的順序存取 ,以保留快取位置並減少爭用。 如需子工作和巢狀工作的詳細資訊,請參閱 附加和中斷連結的子工作

使用本機佇列不僅可降低全域佇列的壓力,還能利用數據位置。 本機佇列中的工作專案經常參考記憶體中實際接近彼此的數據結構。 在這些情況下,第一個工作執行之後,數據已經在快取中,而且可以快速存取。 平行 LINQ (PLINQ)Parallel 類別會廣泛使用巢狀工作和子工作,並使用本機工作佇列來達到顯著的加速。

工作竊取

從 .NET Framework 4 開始,線程集區也提供工作竊取演算法,以協助確保沒有線程閑置,而其他人仍在其佇列中工作。 執行緒集區的執行緒在準備好要處理更多工作時,會先查看自己本機佇列的開頭,接著查看全域佇列,然後再查看其他執行緒的本機佇列。 如果在其他執行緒的本機佇列中發現了工作項目,它會先套用啟發學習法,確定可有效率地執行工作。 如果可以,它會從尾端將工作專案排入佇列(依 FIFO 順序)。 這樣可以減少本機佇列爭用的情形發生,並保留資料位置。 此架構可協助線程集區負載平衡工作比過去版本更有效率。

長時間執行的工作

您可以明確防止將工作放入本機佇列中。 例如,您可能知道某個工作項目會執行相當長的一段時間,而可能阻礙本機佇列上所有其他工作項目的進度。 在這種情況下,您可以指定 System.Threading.Tasks.TaskCreationOptions 選項,提示排程器可能需要再加入一個執行緒來執行工作,才不會阻礙本機佇列上其他執行緒或工作項目的進度。 使用此選項可完全避免線程集區,包括全域和本機佇列。

任務內嵌

在某些情況下,當等候 Task 時,您可以在執行等候作業的執行緒上以同步方式執行它。 這樣可以提升效能,因為可以改為使用現有的執行緒,而不需要使用會被封鎖的額外執行緒。 為防止因重新進入而導致的錯誤,只有當在相關執行緒的本機佇列中發現等候目標時,才會發生工作內嵌。

指定同步處理內容

您可以使用 TaskScheduler.FromCurrentSynchronizationContext 方法,指定應該將工作排定在特定執行緒上執行。 在 Windows Form 和 Windows Presentation Foundation 等架構中,只有在當初建立使用者介面物件的執行緒上執行的程式碼能夠存取該 UI 物件,因此很適合使用這個方法。

下列範例會使用 TaskScheduler.FromCurrentSynchronizationContext Windows Presentation Foundation (WPF) 應用程式中的 方法,在建立使用者介面 (UI) 控件的相同線程上排程工作。 此範例會建立從指定目錄隨機選取的影像馬賽克。 WPF 對象可用來載入和調整影像大小。 然後,原始圖元會傳遞至使用 For 迴圈將像素數據寫入大型單一位元組陣列的工作。 不需要同步處理,因為沒有兩個磚佔用相同的陣列元素。 磚也可以依任何順序撰寫,因為它們的位置會獨立於任何其他磚計算。 然後,大型數位會傳遞至在UI線程上執行的工作,其中像素數據會載入 Image 控制件。

此範例會將數據移出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 為 「Make a馬賽克」,並將其屬性指定 Width 為 「100」。。 Click 連線 事件,並將 button_Click 事件處理程式新增Click="button_Click"<Button> 專案,以在範例的程式代碼中定義。 在 [屬性] 視窗的 [名稱] 文本框中,將控件命名為 “button”。

  3. 以本範例中的程式代碼取代MainWindow.xaml.cs或MainWindow.xaml.vb檔案的整個內容。 針對 C# WPF 專案,請確定工作區的名稱符合項目名稱。

  4. 此範例會從名為 C:\Users\Public\Pictures\Sample Pictures 的目錄讀取 JPEG 影像。 建立目錄並將某些映像放在其中,或變更路徑以參考包含映像的其他目錄。

此範例有一些限制。 例如,僅支援每像素 32 位影像;在重設大小作業期間,物件會損毀其他格式的 BitmapImage 影像。 此外,來源影像必須全部大於磚大小。 為了進一步練習,您可以新增功能來處理多個圖元格式和檔案大小。