Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.
Die TaskScheduler Klasse stellt einen Aufgabenplaner dar. Ein Aufgabenplaner stellt sicher, dass die Arbeit eines Vorgangs 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. Es sollte für die meisten Szenarien ausreichend sein.
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, der häufig die Form paralleler Aufgaben und Abfragen annimmt.
Die globale Warteschlange im Vergleich zu lokalen Warteschlangen
Der Threadpool verwaltet eine globale FIFO-Arbeitswarteschlange (first-in, first-out) für Threads in jeder Anwendungsdomäne. Wann immer ein Programm die ThreadPool.QueueUserWorkItem (oder ThreadPool.UnsafeQueueUserWorkItem) Methode aufruft, wird die Arbeit in diese gemeinsame Warteschlange gestellt und schließlich in den nächsten verfügbaren Thread aus der Warteschlange genommen. 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 Arbeitselemente in die Warteschlange eingereiht und aus der Warteschlange entfernt werden. Dieser Leistungsvorteil steht allen Programmen zur Verfügung, die den Threadpool verwenden.
Vorgänge auf oberster Ebene, bei denen es sich um Aufgaben handelt, die nicht im Kontext einer anderen Aufgabe erstellt werden, werden wie jede andere Arbeitsaufgabe in der globalen Warteschlange platziert. 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 Last-In-First-Out-Reihenfolge (LIFO) zugegriffen, um die Cachelokalität beizubehalten und die Konkurrenz zu verringern. Weitere Informationen zu untergeordneten Aufgaben und geschachtelten Aufgaben 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 physisch nah beieinander im Speicher liegen. 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 Arbeitsverteilungsalgorithmus, damit sichergestellt werden kann, dass keine Threads im Leerlauf sitzen, während andere noch Arbeit in ihren Warteschlangen haben. 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 eine Arbeitsaufgabe in der lokalen Warteschlange eines anderen Threads gefunden wird, wendet sie zuerst Heuristiken an, um sicherzustellen, dass sie die Arbeit effizient ausführen kann. Wenn es möglich ist, wird der Arbeitsgegenstand vom Ende aus der Warteschlange entfernt (in FIFO-Reihenfolge). Dies reduziert die Konsion in jeder lokalen Warteschlange und behält die Datenlokalität bei. Diese Architektur hilft dem Lastenausgleich des Threadpools, effizienter zu arbeiten als bei früheren Versionen.
Lang andauernde Aufgaben
Möglicherweise möchten Sie explizit verhindern, dass eine Aufgabe in eine lokale Warteschlange versetzt wird. Möglicherweise wissen Sie beispielsweise, dass ein bestimmter Arbeitsvorgang relativ lange ausgeführt wird und wahrscheinlich alle anderen Arbeitsvorgänge in der lokalen Warteschlange blockiert. In diesem Fall können Sie die System.Threading.Tasks.TaskCreationOptions Option angeben, die dem Planer einen Hinweis gibt, dass für die Aufgabe möglicherweise ein zusätzlicher Thread erforderlich ist, sodass der Vorwärtsfortschritt anderer Threads oder Arbeitsaufgaben 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 auf ein Task gewartet wird, kann es synchron auf dem Thread ausgeführt werden, der wartet. 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 von Wiedereintritt zu vermeiden, findet Aufgaben-Inlining nur statt, wenn das Warteziel in der lokalen Warteschlange des betreffenden Threads gefunden wird.
Angeben eines Synchronisierungskontexts
Mit der TaskScheduler.FromCurrentSynchronizationContext Methode können Sie angeben, dass eine Aufgabe für einen bestimmten Thread ausgeführt werden soll. Dies ist nützlich in Frameworks wie Windows Forms und Windows Presentation Foundation, bei denen der Zugriff auf Benutzeroberflächenobjekte häufig auf Code beschränkt ist, der im selben Thread ausgeführt wird, auf dem das UI-Objekt 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 rohen Pixel werden dann an eine Aufgabe übergeben, die eine For Schleife verwendet, um die Pixeldaten in ein großes einzelnes Byte-Array 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 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 Sie das Click-Ereignis mit dem im Code des Beispiels definierten Ereignishandler
button_Click
, indem SieClick="button_Click"
zum<Button>
-Element hinzufügen. 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-Per-Pixel-Bilder unterstützt; Bilder in anderen Formaten werden während der Größenänderung 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.