System.Threading.Tasks.TaskScheduler-Klasse
Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.
Die TaskScheduler Klasse stellt einen Aufgabenplaner dar. Durch einen Taskplaner wird sichergestellt, dass die Arbeit einer Aufgabe schließlich ausgeführt wird.
Der Standardmäßige Aufgabenplaner bietet Arbeitsdiebstahl für Lastenausgleich, Threadeinfügung/Deaktivierung für maximalen Durchsatz und insgesamt gute Leistung. Dies ist für die meisten Szenarien in der Regel ausreichend.
Die TaskScheduler Klasse dient auch als Erweiterungspunkt für alle anpassbaren Planungslogiken. Dies umfasst Mechanismen wie das Planen einer Aufgabe für die Ausführung und die Art und Art, wie geplante Vorgänge für Debugger verfügbar gemacht werden sollen. Wenn Sie spezielle Funktionen benötigen, können Sie einen benutzerdefinierten Zeitplan erstellen und für bestimmte Aufgaben oder Abfragen aktivieren.
Der Standardaufgabenplaner und der Threadpool
Der Standardzeitplaner für die Task Parallel Library und PLINQ verwendet den .NET-Threadpool, der durch die ThreadPool Klasse dargestellt wird, um Arbeit in die Warteschlange zu stellen und auszuführen. Der Threadpool verwendet die Informationen, die vom Task Typ bereitgestellt werden, um den differenzierten Parallelismus (kurzlebige Arbeitseinheiten) effizient zu unterstützen, die häufig parallele Aufgaben und Abfragen darstellen.
Die globale Warteschlange im Vergleich zu lokalen Warteschlangen
Der Threadpool Standard eine globale FIFO-Arbeitswarteschlange (first-in, first-out) für Threads in jeder Anwendung enthalten Standard. Wenn ein Programm die ThreadPool.QueueUserWorkItem (oder ThreadPool.UnsafeQueueUserWorkItem) Methode aufruft, wird die Arbeit an dieser freigegebenen Warteschlange platziert und schließlich in den nächsten Thread dewarteschlangen, der verfügbar wird. Ab .NET Framework 4 verwendet diese Warteschlange einen sperrfreien Algorithmus, der der ConcurrentQueue<T> Klasse ähnelt. Durch die Verwendung dieser sperrfreien Implementierung verbringt der Threadpool weniger Zeit, wenn arbeitsbezogene Elemente in die Warteschlange gestellt und nicht in die Warteschlange gestellt werden. Dieser Leistungsvorteil steht allen Programmen zur Verfügung, die den Threadpool verwenden.
Aufgaben der obersten Ebene, bei denen es sich um Aufgaben handelt, die nicht im Kontext einer anderen Aufgabe erstellt werden, werden wie jedes andere Arbeitselement in der globalen Warteschlange abgelegt. Geschachtelte oder untergeordnete Aufgaben, die im Kontext einer anderen Aufgabe erstellt werden, werden hingegen anders behandelt. Ein untergeordnetes Element oder eine geschachtelte Aufgabe wird in einer lokalen Warteschlange abgelegt, die speziell für den Thread vorgesehen ist, in dem die übergeordnete Aufgabe ausgeführt wird. Die übergeordnete Aufgabe ist möglicherweise eine Aufgabe der obersten Ebene. Es kann sich jedoch auch um das untergeordnete Element einer anderen Aufgabe handeln. Wenn dieser Thread für weitere Arbeitsvorgänge bereit ist, wird zuerst eine Suche in der lokalen Warteschlange ausgeführt. Wenn darin Arbeitselemente warten, kann darauf schnell zugegriffen werden. Auf die lokalen Warteschlangen wird in der letzten, first-out-Reihenfolge (First-Out Order, LIFO) zugegriffen, um die Cachelokalität beizubehalten und den Inhalt zu verringern. Weitere Informationen zu untergeordneten Aufgaben und geschachtelten Vorgängen finden Sie unter "Angefügte und getrennte untergeordnete Aufgaben".
Die Verwendung lokaler Warteschlangen verringert nicht nur den Druck auf die globale Warteschlange, sondern nutzt auch die Datenlokalität. Arbeitselemente in der lokalen Warteschlange verweisen häufig auf Datenstrukturen, die sich physisch in der Nähe des Speichers befinden. In diesen Fällen befinden sich die Daten bereits im Cache, nachdem die erste Aufgabe ausgeführt wurde und schnell darauf zugegriffen werden kann. Sowohl Parallel LINQ (PLINQ) als auch die Parallel Klasse verwenden geschachtelte Aufgaben und untergeordnete Aufgaben umfassend und erzielen mithilfe der lokalen Arbeitswarteschlangen erhebliche Geschwindigkeiten.
Arbeit stehlen
Ab .NET Framework 4 verfügt der Threadpool auch über einen Arbeitsdiebstahlalgorithmus, um sicherzustellen, dass keine Threads im Leerlauf sitzen, während andere noch in ihren Warteschlangen arbeiten. Wenn ein Threadpoolthread zusätzliche Arbeit übernehmen kann, wird zuerst am Anfang der lokalen Warteschlange, anschließend in der globalen Warteschlange und zuletzt in den lokalen Warteschlangen anderer Threads gesucht. Wenn ein Arbeitselement in der lokalen Warteschlange eines anderen Threads gefunden wird, wird zunächst Heuristik angewendet, um sicherzustellen, dass die Arbeit effizient ausgeführt werden kann. Wenn dies möglich ist, wird die Arbeitsaufgabe vom Tail (in FIFO-Reihenfolge) in die Warteschlange gestellt. Dadurch werden Konflikte in jeder lokalen Warteschlange verringert und der Datenort beibehalten. Diese Architektur hilft dem Lastenausgleich des Threadpools, effizienter zu arbeiten als bei früheren Versionen.
Lange ausgeführte Aufgaben
Unter Umständen möchten Sie ausdrücklich verhindern, dass eine Aufgabe in einer lokalen Warteschlange abgelegt wird. Sie wissen möglicherweise z. B., dass ein bestimmtes Arbeitselement für eine relativ lange Zeit ausgeführt wird und daher wahrscheinlich alle anderen Arbeitselemente in der lokalen Warteschlange blockiert. In diesem Fall können Sie die System.Threading.Tasks.TaskCreationOptions-Option angeben, die den Planer darauf hinweist, dass u. U. ein weiterer Thread für die Aufgabe erforderlich ist, damit der Fortschritt anderer Threads oder Arbeitselemente in der lokalen Warteschlange nicht blockiert wird. Mit dieser Option vermeiden Sie den Threadpool vollständig, einschließlich der globalen und lokalen Warteschlangen.
Vorgangslining
In einigen Fällen, in denen ein Task Warten ausgeführt wird, kann es synchron auf dem Thread ausgeführt werden, der den Wartevorgang ausführt. Dadurch wird die Leistung verbessert, indem die Notwendigkeit eines zusätzlichen Threads verhindert und stattdessen der vorhandene Thread verwendet wird, der andernfalls blockiert würde. Um Fehler aufgrund einer Neutraabilität zu vermeiden, tritt die Vorgangslining nur auf, wenn das Warteziel in der lokalen Warteschlange des relevanten Threads gefunden wird.
Angeben eines Synchronisierungskontexts
Sie können mit der TaskScheduler.FromCurrentSynchronizationContext-Methode angeben, dass eine Aufgabe für die Ausführung in einem bestimmten Thread geplant werden soll. Dies ist in Frameworks wie Windows Forms und Windows Presentation Foundation hilfreich, wo der Zugriff auf Benutzeroberflächenobjekte oftmals auf Code beschränkt ist, der in dem Thread ausgeführt wird, in dem das Benutzeroberflächenobjekt erstellt wurde.
Im folgenden Beispiel wird die TaskScheduler.FromCurrentSynchronizationContext Methode in einer Windows Presentation Foundation (WPF)-App verwendet, um einen Vorgang im selben Thread zu planen, auf dem das Ui-Steuerelement erstellt wurde. Im Beispiel wird ein Mosaik von Bildern erstellt, die zufällig aus einem angegebenen Verzeichnis ausgewählt werden. Die WPF-Objekte werden zum Laden und Ändern der Größe der Bilder verwendet. Die Unformatierten Pixel werden dann an eine Aufgabe übergeben, die eine For Schleife verwendet, um die Pixeldaten in ein großes Einzelbytearray zu schreiben. Es ist keine Synchronisierung erforderlich, da keine zwei Kacheln dieselben Arrayelemente belegen. Die Kacheln können auch in beliebiger Reihenfolge geschrieben werden, da ihre Position unabhängig von jeder anderen Kachel berechnet wird. Das große Array wird dann an eine Aufgabe übergeben, die im UI-Thread ausgeführt wird, wobei die Pixeldaten in ein Image-Steuerelement geladen werden.
Das Beispiel verschiebt Daten aus dem UI-Thread, ändert sie mithilfe paralleler Schleifen und Task Objekte und übergibt sie dann an eine Aufgabe, die im UI-Thread ausgeführt wird. Dieser Ansatz ist nützlich, wenn Sie die Parallele Aufgabenbibliothek verwenden müssen, um Vorgänge auszuführen, die von der WPF-API nicht unterstützt werden oder nicht ausreichend schnell sind. Eine weitere Möglichkeit zum Erstellen eines Bildmosaiks in WPF besteht darin, ein System.Windows.Controls.WrapPanel Steuerelement zu verwenden und Bilder hinzuzufügen. Die WrapPanel Handhabt die Arbeit der Positionierung der Kacheln. Diese Arbeit kann jedoch nur im UI-Thread ausgeführt werden.
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
Erstellen Sie zum Erstellen des Beispiels ein WPF-Anwendungsprojekt in Visual Studio, und nennen Sie es WPF_CS1 (für ein C#-WPF-Projekt) oder WPF_VB1 (für ein Visual Basic WPF-Projekt). Gehen Sie wie folgt vor:
Ziehen Sie in der Entwurfsansicht ein Image Steuerelement aus der Toolbox in die obere linke Ecke der Entwurfsoberfläche. Nennen Sie im Textfeld "Name" des Eigenschaftenfensters das Steuerelement "Image".
Ziehen Sie ein Button Steuerelement aus der Toolbox in den unteren linken Teil des Anwendungsfensters. Geben Sie in der XAML-Ansicht die Content Eigenschaft der Schaltfläche als "Mosaik erstellen" an, und geben Sie dessen Width Eigenschaft als "100" an. Verbinden das Click Ereignis mit dem
button_Click
im Code des Beispiels definierten Ereignishandler durch HinzufügenClick="button_Click"
zum<Button>
Element. Nennen Sie im Textfeld "Name " des Eigenschaftenfensters das Steuerelement "Schaltfläche".Ersetzen Sie den gesamten Inhalt der datei MainWindow.xaml.cs oder MainWindow.xaml.vb durch den Code aus diesem Beispiel. Stellen Sie für ein C#-WPF-Projekt sicher, dass der Name des Arbeitsbereichs dem Projektnamen entspricht.
Im Beispiel werden JPEG-Bilder aus einem Verzeichnis mit dem Namen "C:\Users\Public\Pictures\Sample Pictures" gelesen. Erstellen Sie entweder das Verzeichnis, und platzieren Sie einige Bilder darin, oder ändern Sie den Pfad, um auf ein anderes Verzeichnis zu verweisen, das Bilder enthält.
In diesem Beispiel gibt es einige Einschränkungen. Beispielsweise werden nur 32-Bit-Pro-Pixel-Bilder unterstützt; Bilder in anderen Formaten sind während des Größenänderungsvorgangs durch das BitmapImage Objekt beschädigt. Außerdem müssen die Quellbilder größer als die Kachelgröße sein. Als weitere Übung können Sie Funktionen hinzufügen, um mehrere Pixelformate und Dateigrößen zu verarbeiten.