Pool de threads managés
Mise à jour : novembre 2007
La classe ThreadPool fournit à votre application un pool de threads de travail qui sont managés par le système, vous permettant de vous concentrer sur les tâches de l'application plutôt que sur la gestion des threads. Dans le cas de petites tâches qui exigent un traitement en arrière-plan, le pool de threads managé représente un moyen facile de tirer parti de plusieurs threads.
Remarque : |
---|
Démarrant avec le .NET Framework version 2.0 Service Pack 1, le débit du pool de threads est amélioré considérablement dans les trois zones clés identifiées comme goulots d'étranglement dans les version finale précédentes du .NET Framework: mettre en file d'attente des tâches, distribuer des threads ThreadPool, et distribuer des threads de terminaison d'E/S. Pour utiliser cette fonctionnalité, votre application doit cibler .NET Framework version 3.5. Pour plus d'informations, consultez Architecture de .NET Framework 3.5. |
Pour les tâches en arrière-plan qui interagissent avec l'interface utilisateur, le .NET Framework version 2.0 fournit également la classe BackgroundWorker qui communique à l'aide d'événements déclenchés sur le thread d'interface utilisateur.
Le .NET Framework utilise des threads du pool de threads dans de nombreuses circonstances, notamment pour la terminaison des E/S asynchrones, les rappels de la minuterie, les opérations d'attente inscrites, les appels de méthodes asynchrones utilisant des délégués, et des connexions de socket System.Net.
Scénarios où l'utilisation de threads du pool de threads est déconseillée
Il existe plusieurs scénarios où il est plus approprié de créer et gérer vos propres threads au lieu d'utiliser les threads du pool de threads :
Vous avez besoin d'un thread de premier plan.
Il est nécessaire que le thread ait une priorité particulière.
Certaines tâches provoquent le blocage du thread pendant de longues périodes. Le pool de threads ayant un nombre maximal de threads, un grand nombre de threads bloqués dans le pool peut empêcher le démarrage de tâches.
Vous devez placer des threads dans un thread cloisonné (STA, Single-Threaded Apartment). Tous les threads de ThreadPool sont dans le MTA (Multithreaded Apartment).
Une identité stable doit être associée au thread ou un thread doit être dédié à une tâche.
Caractéristiques du pool de threads
Les threads du pool de threads sont des threads d'arrière-plan. Consultez Threads de premier plan et d'arrière-plan. Chaque thread utilise la taille de pile et l'ordre de priorité d'exécution par défaut. Il se trouve dans le MTA (Multithreaded Apartment).
Il existe un seul pool de threads par processus.
Exceptions dans les threads d'un pool de threads
Les exceptions non gérées sur les threads du pool de threads mettent un terme au processus. Il existe trois exceptions à cette règle :
Un ThreadAbortException est levé dans un thread de pool de threads en raison d'un appel à Abort.
Un AppDomainUnloadedException est levé dans un thread du pool de threads, car le domaine d'application est en cours de déchargement.
Le Common Language Runtime ou un processus hôte met fin au thread.
Pour plus d'informations, consultez Exceptions dans les threads managés.
Remarque : |
---|
Dans les versions 1.0 et 1.1 du .NET Framework, le Common Language Runtime intercepte en mode silencieux les exceptions non gérées dans les threads du pool de threads. Cela peut endommager l'état de l'application et éventuellement provoquer le blocage des applications, ce qui peut être très difficile à déboguer. |
Nombre maximal de threads de pool de threads
Le nombre des opérations qui peuvent être mises en file d'attente dans le pool de threads est limité uniquement par la mémoire disponible ; toutefois, le pool de threads limite le nombre de threads simultanément actifs dans le processus. Par défaut, la limite est fixée à 25 threads de travail par unité centrale et à 1 000 threads de terminaison d'E/S.
Vous pouvez contrôler le nombre maximal de threads en utilisant les méthodes GetMaxThreads et SetMaxThreads.
Remarque : |
---|
Dans les versions 1.0 et 1.1 du .NET Framework, la taille du pool de threads ne peut pas être définie à partir du code managé. Le code qui héberge le Common Language Runtime peut définir cette taille à l'aide de CorSetMaxThreads, défini dans mscoree.h. |
Nombre minimal de threads inactifs
Le pool de threads conserve également un nombre minimal de threads disponibles, même lorsque tous les threads sont inactifs, afin que les tâches mises en file d'attente puissent démarrer immédiatement. Les threads inactifs au-delà de cette limite minimale sont arrêtés, afin d'économiser les ressources système. Par défaut, un seul thread inactif est conservé par processeur.
Le pool de threads possède un délai prédéfini (une demi-seconde dans le .NET Framework version 2.0) avant de démarrer de nouveaux threads inactifs. Si votre application démarre périodiquement de nombreuses tâches dans un délai très court, une légère augmentation du nombre de threads inactifs peut se traduire par une augmentation significative du débit. La définition d'un nombre trop élevé de threads inactifs consomme inutilement les ressources système.
Vous pouvez contrôler le nombre de threads inactifs conservés par le pool de threads à l'aide des méthodes GetMinThreads et SetMinThreads.
Remarque : |
---|
Dans la version 1.0 du .NET Framework, le nombre minimal de threads inactifs ne peut pas être défini. |
Vérifications de la sécurité ignorées
Le pool de threads fournit également les ThreadPool.UnsafeQueueUserWorkItem et méthodes ThreadPool.UnsafeRegisterWaitForSingleObject. Utilisez ces méthodes uniquement lorsque vous êtes certain que la pile de l'appelant n'est pas concernée par toutes les vérifications de la sécurité effectuées pendant l'exécution de la tâche en file d'attente. Les méthodes QueueUserWorkItem et RegisterWaitForSingleObject capturent toutes deux la pile de l'appelant, qui est fusionnée dans la pile du thread du pool de threads lorsque le thread commence à exécuter une tâche. Si une vérification de la sécurité est nécessaire, l'ensemble de la pile doit être vérifié. Bien que la vérification assure la sécurité, cette vérification s'effectue au détriment des performances.
Utilisation du pool de threads
Vous pouvez utiliser le pool de threads en appelant ThreadPool.QueueUserWorkItem à partir du code managé (ou CorQueueUserWorkItem à partir du code non managé) et en passant un délégué WaitCallback représentant la méthode qui exécute la tâche. Vous pouvez également mettre en file d'attente des éléments de travail qui sont rattachés à une opération d'attente en utilisant ThreadPool.RegisterWaitForSingleObject et en passant un WaitHandle qui, une fois signalé ou expiré, déclenche un appel à la méthode représentée par le délégué WaitOrTimerCallback. Dans les deux cas, le pool de threads utilise un thread d'arrière-plan pour appeler la méthode de rappel.
Exemples de ThreadPool
Les trois exemples de code suivants illustrent les méthodes QueueUserWorkItem et RegisterWaitForSingleObject.
Le premier exemple met en file d'attente une tâche très simple représentée par la méthode ThreadProc, en utilisant la méthode QueueUserWorkItem.
Imports System
Imports System.Threading
Public Class Example
Public Shared Sub Main()
' Queue the task.
ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc))
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the thread pool task runs. The thread pool uses background
' threads, which do not keep the application running. (This
' is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
End Sub
' This thread procedure performs the task.
Shared Sub ThreadProc(stateInfo As Object)
' No state object was passed to QueueUserWorkItem, so
' stateInfo is null.
Console.WriteLine("Hello from the thread pool.")
End Sub
End Class
using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
Indication des données de tâche pour QueueUserWorkItem
L'exemple de code suivant utilise la méthode QueueUserWorkItem pour mettre une tâche en file d'attente et fournir les données pour cette tâche.
Imports System
Imports System.Threading
' TaskInfo holds state information for a task that will be
' executed by a ThreadPool thread.
Public Class TaskInfo
' State information for the task. These members
' can be implemented as read-only properties, read/write
' properties with validation, and so on, as required.
Public Boilerplate As String
Public Value As Integer
' Public constructor provides an easy way to supply all
' the information needed for the task.
Public Sub New(text As String, number As Integer)
Boilerplate = text
Value = number
End Sub
End Class
Public Class Example
Public Shared Sub Main()
' Create an object containing the information needed
' for the task.
Dim ti As New TaskInfo("This report displays the number {0}.", 42)
' Queue the task and data.
If ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc), ti) Then
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the ThreadPool task has a chance to run. ThreadPool uses
' background threads, which do not keep the application
' running. (This is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
Else
Console.WriteLine("Unable to queue ThreadPool request.")
End If
End Sub
' The thread procedure performs the independent task, in this case
' formatting and printing a very simple report.
'
Shared Sub ThreadProc(stateInfo As Object)
Dim ti As TaskInfo = CType(stateInfo, TaskInfo)
Console.WriteLine(ti.Boilerplate, ti.Value)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
// State information for the task. These members
// can be implemented as read-only properties, read/write
// properties with validation, and so on, as required.
public string Boilerplate;
public int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
public TaskInfo(string text, int number) {
Boilerplate = text;
Value = number;
}
}
public class Example {
public static void Main() {
// Create an object containing the information needed
// for the task.
TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the ThreadPool task has a chance to run. ThreadPool uses
// background threads, which do not keep the application
// running. (This is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
else {
Console.WriteLine("Unable to queue ThreadPool request.");
}
}
// The thread procedure performs the independent task, in this case
// formatting and printing a very simple report.
//
static void ThreadProc(Object stateInfo) {
TaskInfo ti = (TaskInfo) stateInfo;
Console.WriteLine(ti.Boilerplate, ti.Value);
}
}
RegisterWaitForSingleObject
L'exemple suivant illustre plusieurs fonctionnalités de threading :
Mise en file d'attente une tâche pour son exécution par les threads ThreadPool, avec la méthode RegisterWaitForSingleObject.
Signalisation d'une tâche à exécuter, avec AutoResetEvent. Consultez EventWaitHandle, AutoResetEvent et ManualResetEvent.
Gestion des délais et des signaux avec un délégué WaitOrTimerCallback.
Annulation d'une tâche mise en file d'attente avec RegisteredWaitHandle.
Imports System
Imports System.Threading
' TaskInfo contains data that will be passed to the callback
' method.
Public Class TaskInfo
public Handle As RegisteredWaitHandle = Nothing
public OtherInfo As String = "default"
End Class
Public Class Example
Public Shared Sub Main()
' The main thread uses AutoResetEvent to signal the
' registered wait handle, which executes the callback
' method.
Dim ev As New AutoResetEvent(false)
Dim ti As New TaskInfo()
ti.OtherInfo = "First task"
' The TaskInfo for the task includes the registered wait
' handle returned by RegisterWaitForSingleObject. This
' allows the wait to be terminated when the object has
' been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject( _
ev, _
New WaitOrTimerCallback(AddressOf WaitProc), _
ti, _
1000, _
false _
)
' The main thread waits about three seconds, to demonstrate
' the time-outs on the queued task, and then signals.
Thread.Sleep(3100)
Console.WriteLine("Main thread signals.")
ev.Set()
' The main thread sleeps, which should give the callback
' method time to execute. If you comment out this line, the
' program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000)
' If you start a thread yourself, you can wait for it to end
' by calling Thread.Join. This option is not available with
' thread pool threads.
End Sub
' The callback method executes when the registered wait times out,
' or when the WaitHandle (in this case AutoResetEvent) is signaled.
' WaitProc unregisters the WaitHandle the first time the event is
' signaled.
Public Shared Sub WaitProc(state As Object, timedOut As Boolean)
' The state object must be cast to the correct type, because the
' signature of the WaitOrTimerCallback delegate specifies type
' Object.
Dim ti As TaskInfo = CType(state, TaskInfo)
Dim cause As String = "TIMED OUT"
If Not timedOut Then
cause = "SIGNALED"
' If the callback method executes because the WaitHandle is
' signaled, stop future execution of the callback method
' by unregistering the WaitHandle.
If Not ti.Handle Is Nothing Then
ti.Handle.Unregister(Nothing)
End If
End If
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", _
ti.OtherInfo, _
Thread.CurrentThread.GetHashCode().ToString(), _
cause _
)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public class Example {
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
1000,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}