本文提供此 API 參考文件的補充備註。
類別 TaskScheduler 代表工作排程器。 工作排程器可確保工作最終會執行。
預設工作排程器提供工作竊取以達到負載平衡、線程插入/淘汰以達到最大輸送量,以及整體優良的效能。 它應該足以應付大部分的案例。
類別 TaskScheduler 也會作為所有可自定義排程邏輯的延伸點。 這包括一些機制,例如如何排程工作以供執行,以及排程工作應如何公開給調試程式。 如果您需要特殊功能,您可以建立自定義排程器,並針對特定工作或查詢加以啟用。
預設工作排程器和線程集區
工作平行庫和 PLINQ 的預設排程器會使用 ThreadPool 類別所代表的 .NET 執行緒集區來排入佇列及執行工作。 執行緒集區使用由Task型別提供的資訊,高效支援平行任務和查詢中常見的精細平行處理(短期工作單位)。
全域佇列對比本地佇列
線程集區會針對每個應用程式域中的線程維護全域 FIFO(先出先出)工作佇列。 每當程式呼叫 ThreadPool.QueueUserWorkItem(或 ThreadPool.UnsafeQueueUserWorkItem)方法時,工作就會放入這個共用佇列,然後轉給下一個可供使用的線程處理。 從 .NET Framework 4 開始,此佇列會使用類似 類別的 ConcurrentQueue<T> 無鎖定演算法。 藉由使用此無鎖定實作,執行緒池在佇列和出列工作項目時會花費較少的時間。 此效能優點可供使用線程集區的所有程式使用。
最上層任務,即不在其他任務上下文中建立的任務,會像其他工作項目一樣,放置在全域佇列中。 不過,在另一個工作環境中建立的巢狀工作或子工作,其處理方式將有所不同。 子工作或巢狀工作會放在本機佇列上,而該佇列是父工作執行所在的線程。 父任務可能是頂層任務,也可能是另一個任務的一部分。 當此線程準備好進行更多工作時,它會先在本機佇列中尋找。 如果工作項目處於待命狀態,可以快速存取這些項目。 本機佇列會以最後先出的順序存取 ,以保留快取位置並減少爭用。 如需子工作和巢狀工作的詳細資訊,請參閱 附加和獨立的子工作。
使用本機佇列不僅可降低全域佇列的壓力,還能利用資料區域性。 本機佇列中的工作專案經常參考記憶體中實際彼此接近的資料結構。 在這些情況下,第一個工作執行之後,數據已經在快取中,而且可以快速存取。 平行 LINQ (PLINQ) 和 Parallel 類別會廣泛使用巢狀工作和子工作,並使用本機工作佇列來達到顯著的加速。
工作竊取
從 .NET Framework 4 開始,執行緒集區也提供工作竊取算法,以協助確保沒有執行緒閒置,而其他執行緒的佇列中仍有工作。 當線程池中的線程準備好接受更多工作時,它會先查看自己的本地佇列的開頭,然後查看全域佇列,最後再查看其他線程的本地佇列。 如果它在另一個線程的本機佇列中找到工作專案,它會先套用啟發學習法,以確保它可以有效率地執行工作。 如果可以的話,它會從尾端將工作項目自佇列中取出(依 FIFO 順序)。 這樣可減少每個本地佇列的爭用,並保留資料區域性。 此架構可協助線程集區負載平衡工作比過去版本更有效率。
長時間執行的工作
您可能想要特意防止任務被放在本機佇列上。 例如,您可能知道特定工作專案會執行相當長的時間,而且可能會封鎖本機佇列上所有其他工作專案。 在此情況下,您可以指定 System.Threading.Tasks.TaskCreationOptions 選項,這個選項會為排程器提供提示,指出工作可能需要額外的線程,使其不會封鎖本機佇列上其他線程或工作專案的向前進度。 使用此選項可完全避免線程池,包括全局和本地佇列。
任務內嵌
在某些情況下,當Task等候時,可能會在執行等候作業的線程上同步地執行。 這可藉由避免需求額外的線程,並使用現有的線程來增強效能,否則該線程將會被封鎖。 為了避免因重新進入而發生錯誤,只有在相關執行緒的本地佇列中找到等候目標時,才會發生任務內嵌。
指定同步處理內容
您可以使用 TaskScheduler.FromCurrentSynchronizationContext 方法來指定工作應該排程在特定線程上執行。 這在 Windows Forms 和 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 專案)。 然後執行以下動作:
在設計檢視中,將控件從 Image] 拖曳到設計介面的左上角。 在 [屬性] 視窗的 [名稱] 文本框中,將控件命名為 “image”。
將控件從 Button] 拖曳至應用程式視窗左下半部。 在 XAML 檢視中,將按鈕的 Content 屬性指定為「製作拼貼」,並將其 Width 屬性指定為「100」。 將 Click 事件與範例代碼中定義的
button_Click事件處理程式連接起來,方法是將Click="button_Click"新增至<Button>元素。 在 [屬性] 視窗的 [名稱] 文本框中,將控件命名為 “button”。以本範例中的程式代碼取代MainWindow.xaml.cs或MainWindow.xaml.vb檔案的整個內容。 針對 C# WPF 專案,請確定工作區的名稱符合項目名稱。
此範例會從名為 C:\Users\Public\Pictures\Sample Pictures 的目錄讀取 JPEG 影像。 建立目錄並將某些映像放在其中,或變更路徑以參考包含映像的其他目錄。
此範例有一些限制。 例如,僅支援每像素 32 位的影像。BitmapImage 物件在重設大小作業期間會損毀其他格式的影像。 此外,來源影像必須全部大於磚大小。 為了進一步練習,您可以新增功能來處理多個圖元格式和檔案大小。