Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article fournit des remarques supplémentaires à la documentation de référence de cette API.
La TaskScheduler classe représente un planificateur de tâches. Un planificateur de tâches garantit que le travail d’une tâche est finalement exécuté.
Le planificateur de tâches par défaut fournit le vol de travail pour l’équilibrage de charge, l’injection/mise hors service de threads pour un débit maximal et de bonnes performances globales. Il doit être suffisant pour la plupart des scénarios.
La TaskScheduler classe sert également de point d’extension pour toute logique de planification personnalisable. Cela inclut des mécanismes tels que la planification d’une tâche pour l’exécution et la façon dont les tâches planifiées doivent être exposées aux débogueurs. Si vous avez besoin de fonctionnalités spéciales, vous pouvez créer un planificateur personnalisé et l’activer pour des tâches ou des requêtes spécifiques.
Planificateur de tâches par défaut et pool de threads
Le planificateur par défaut de la Bibliothèque de tâches parallèles et de PLINQ utilise le pool de threads de .NET, représenté par la classe ThreadPool, pour mettre en file d’attente et exécuter le travail. Le pool de threads utilise les informations fournies par le Task type pour prendre en charge efficacement le parallélisme affiné (unités de travail de courte durée) que représentent souvent les tâches et requêtes parallèles.
File d’attente globale et files d’attente locales
Le pool de threads gère une file d’attente de travail FIFO globale (premier entré, premier sortie) pour les threads de chaque domaine d’application. Chaque fois qu’un programme appelle la méthode ThreadPool.QueueUserWorkItem (ou ThreadPool.UnsafeQueueUserWorkItem), le travail est placé dans cette file d’attente partagée et finalement retiré de la file d’attente vers le thread suivant qui devient disponible. À compter de .NET Framework 4, cette file d’attente utilise un algorithme sans verrou qui ressemble à la ConcurrentQueue<T> classe. Grâce à cette implémentation sans verrouillage, le pool de threads passe moins de temps à mettre en file d’attente et à retirer des éléments de travail. Cet avantage de performances est disponible pour tous les programmes qui utilisent le pool de threads.
Les tâches de niveau supérieur, qui ne sont pas créées dans le contexte d’une autre tâche, sont placées dans la file d’attente globale comme n’importe quel autre élément de travail. Toutefois, les tâches imbriquées ou enfants, créées dans le contexte d’une autre tâche, sont gérées tout à fait différemment. Une tâche enfant ou imbriquée est placée sur une file d’attente locale spécifique au thread sur lequel la tâche parente s’exécute. La tâche parente peut être une tâche de niveau supérieur ou l’enfant d’une autre tâche. Lorsque ce thread est prêt à effectuer plus de travail, il consulte d'abord la file d'attente locale. Si les éléments de travail y attendent, ils sont accessibles rapidement. Les files d'attente locales sont accessibles selon l'ordre dernier entré, premier sorti (LIFO) pour préserver la localité du cache et réduire la contention. Pour plus d’informations sur les tâches enfants et les tâches imbriquées, consultez Tâches enfants jointes et détachées.
L’utilisation de files d’attente locales réduit non seulement la pression sur la file d’attente globale, mais tire également parti de la localité des données. Les éléments de travail de la file d’attente locale font souvent référence à des structures de données physiquement proches les unes des autres en mémoire. Dans ce cas, les données se trouvent déjà dans le cache après l’exécution de la première tâche et sont accessibles rapidement. Parallel LINQ (PLINQ) et la Parallel classe utilisent largement les tâches imbriquées et les tâches enfants, et effectuent des accélérations significatives à l’aide des files d’attente de travail locales.
Vol de travail
À partir de .NET Framework 4, le pool de threads intègre également un algorithme de répartition du travail, garantissant qu'aucun thread ne reste inactif pendant que d'autres ont encore du travail dans leurs files d'attente. Lorsqu’un thread dans un pool de threads est prêt pour plus de travail, il examine d’abord le début de sa file d’attente locale, puis la file d’attente globale, puis les files d’attente locales d’autres threads. S’il trouve un élément de travail dans la file d’attente locale d’un autre thread, il applique d’abord des heuristiques pour s’assurer qu’il peut exécuter efficacement le travail. Si possible, il désenfile l’élément de travail à partir de la fin (dans l’ordre FIFO). Cela réduit la contention sur chaque file d’attente locale et conserve la localité des données. Cette architecture permet au pool de threads de mieux équilibrer la charge de travail qu'avec les versions précédentes.
Tâches longues
Vous pouvez empêcher explicitement une tâche d’être placée sur une file d’attente locale. Par exemple, vous savez peut-être qu’un élément de travail particulier fonctionnera pour une durée relativement longue et sera à même de bloquer tous les autres éléments de travail sur la file d’attente locale. Dans ce cas, vous pouvez spécifier l’option System.Threading.Tasks.TaskCreationOptions , qui fournit un indicateur au planificateur qu’un thread supplémentaire peut être requis pour la tâche afin qu’elle ne bloque pas la progression vers l’avant d’autres threads ou éléments de travail dans la file d’attente locale. En utilisant cette option, vous évitez complètement le pool de threads, y compris les files d’attente globales et locales.
Inlining des tâches
Dans certains cas, lorsqu’un Task est en attente, il peut être exécuté de manière synchrone sur le thread qui effectue l’opération d’attente. Cela améliore les performances en empêchant la nécessité d’un thread supplémentaire et en utilisant plutôt le thread existant, ce qui aurait été bloqué dans le cas contraire. Pour éviter les erreurs dues à la réentrance, l’inlining des tâches ne se produit que lorsque la cible d’attente se trouve dans la file d’attente locale du thread concerné.
Spécifier un contexte de synchronisation
Vous pouvez utiliser la TaskScheduler.FromCurrentSynchronizationContext méthode pour spécifier qu’une tâche doit être planifiée pour s’exécuter sur un thread particulier. Cela est utile dans les frameworks tels que Windows Forms et Windows Presentation Foundation, où l’accès aux objets d’interface utilisateur est souvent limité au code exécuté sur le même thread sur lequel l’objet d’interface utilisateur a été créé.
L’exemple suivant utilise la TaskScheduler.FromCurrentSynchronizationContext méthode dans une application Windows Presentation Foundation (WPF) pour planifier une tâche sur le même thread que celui sur lequel le contrôle d’interface utilisateur a été créé. L’exemple crée une mosaïque d’images sélectionnées de manière aléatoire à partir d’un répertoire spécifié. Les objets WPF sont utilisés pour charger et redimensionner les images. Les pixels bruts sont ensuite transmis à une tâche qui utilise une boucle For pour écrire les données de pixels dans un grand tableau à un seul octet. Aucune synchronisation n’est requise, car aucune deux vignettes n’occupent les mêmes éléments de tableau. Les vignettes peuvent également être écrites dans n’importe quel ordre, car leur position est calculée indépendamment de toute autre vignette. Le grand tableau est ensuite transmis à une tâche qui s’exécute sur le thread de l’interface utilisateur, où les données de pixels sont chargées dans un contrôle Image.
L’exemple déplace les données hors du thread d’interface utilisateur, les modifie à l’aide de boucles et Task d’objets parallèles, puis les transmet à une tâche qui s’exécute sur le thread d’interface utilisateur. Cette approche est utile lorsque vous devez utiliser la bibliothèque parallèle de tâches pour effectuer des opérations qui ne sont pas prises en charge par l’API WPF ou qui ne sont pas suffisamment rapides. Une autre façon de créer une mosaïque d’images dans WPF consiste à utiliser un System.Windows.Controls.WrapPanel contrôle et à y ajouter des images. WrapPanel gère le positionnement des mosaïques. Toutefois, ce travail ne peut être effectué que sur le thread d’interface utilisateur.
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
Pour créer l’exemple, créez un projet d’application WPF dans Visual Studio et nommez-le WPF_CS1 (pour un projet WPF C#) ou WPF_VB1 (pour un projet WPF Visual Basic). Faites ensuite ce qui suit :
Dans la vue Conception, faites glisser un contrôle Image de la boîte à outils vers le coin supérieur gauche de la surface de conception. Dans la zone de texte Name de la fenêtre Propriétés , nommez le contrôle « image ».
Faites glisser un contrôle Button de la boîte à outils vers la partie inférieure gauche de la fenêtre de l’application. En mode XAML, spécifiez la Content propriété du bouton comme « Créer une mosaïque » et spécifiez sa Width propriété comme « 100 ». Connectez l’événement Click avec le
button_Click
gestionnaire d’événements défini dans le code de l’exemple en ajoutantClick="button_Click"
à l’élément<Button>
. Dans la zone de texte Nom de la fenêtre Propriétés , nommez le contrôle « button ».Remplacez tout le contenu du fichier MainWindow.xaml.cs ou MainWindow.xaml.vb par le code de cet exemple. Pour un projet WPF C#, vérifiez que le nom de l’espace de travail correspond au nom du projet.
L’exemple lit des images JPEG à partir d’un répertoire nommé C :\Users\Public\Pictures\Sample Pictures. Créez le répertoire et placez-y certaines images, ou modifiez le chemin d’accès pour faire référence à un autre répertoire qui contient des images.
Cet exemple présente certaines limitations. Par exemple, seules les images de 32 bits par pixel sont prises en charge ; les images dans d’autres formats sont endommagées par l’objet BitmapImage pendant l’opération de redimensionnement. De plus, les images sources doivent toutes être plus grandes que la taille de la mosaïque. En guise d’exercice supplémentaire, vous pouvez ajouter des fonctionnalités pour gérer plusieurs formats de pixels et tailles de fichier.