HOWTO · Csharp
Arrêter un thread en C#
Dans cet article, je vais vous expliquer comment démarrer et arrêter un thread en C#.
Sur cette page
Lorsqu’une application C# doit arrêter un thread de travail créé avec Thread, l’ancienne méthode Abort() peut demander au runtime de le terminer. Cette technique s’applique uniquement aux runtimes qui prennent en charge Thread.Abort() ; les runtimes .NET modernes peuvent lever PlatformNotSupportedException à la place. Pour le nouveau code, l’annulation coopérative avec un CancellationToken est généralement plus sûre, car le thread de travail peut libérer ses ressources et laisser l’état partagé dans une situation connue.
Sur un runtime pris en charge, Abort() provoque une ThreadAbortException dans le thread cible. Cette exception est asynchrone : le thread de travail peut donc être interrompu alors qu’il détient un verrou ou met à jour des données partagées. Considérez cet exemple comme une référence de compatibilité et utilisez-le uniquement lorsque le runtime cible prend explicitement l’API en charge.
Utilisez la méthode Abort() pour arrêter un thread en C#
La documentation de Thread.Abort() décrit la prise en charge de cette opération héritée selon les runtimes. L’espace de noms System.Threading contient le type Thread ainsi que ses méthodes Start() et Abort(). L’exemple complet ci-dessous définit ThreadDemo.threadAssign(), crée une classe ThreadExampleMain avec Main(), démarre le thread de travail et demande son arrêt.
La boucle est configurée pour six itérations, et non quatre. La ligne Thread is abort est affichée immédiatement avant la demande d’arrêt ; il s’agit donc uniquement d’un message de diagnostic et non d’une preuve que le worker s’est arrêté.
Le code source autonome suivant crée le thread de travail, le démarre, demande son arrêt, puis attend sa fin :
using System;
using System.Threading;
public class ThreadDemo {
public void threadAssign() {
for (int threadCount = 0; threadCount < 6; threadCount++) {
Console.WriteLine("First Thread");
Thread.Sleep(10);
}
}
}
public class ThreadExampleMain {
public static void Main() {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(new ThreadStart(threadDemo.threadAssign));
thread.Start();
Console.WriteLine("Thread is abort");
try {
thread.Abort();
}
catch (PlatformNotSupportedException) {
Console.WriteLine("Abort() is not supported by this runtime.");
}
thread.Join();
}
}
Thread is abort
First Thread
Les éléments d’exécution fournis consignent une exécution terminée de ce code source. Ils confirment que Thread is abort est affiché avant la demande d’arrêt ; l’ordre et le nombre de lignes First Thread dépendent de l’ordonnancement, et il ne faut donc pas s’attendre à une transcription fixe. Exécutez le code sur le runtime que vous ciblez avant de vous fier à son comportement.
Sur un runtime qui prend en charge Abort(), le thread de travail reçoit la ThreadAbortException asynchrone et s’arrête. Cela se distingue d’une exception levée de manière synchrone par Abort() dans Main(). L’ordre exact et le nombre de lignes First Thread affichées avant la demande dépendent de l’ordonnancement des threads ; la boucle autorise elle-même six itérations. Si Abort() lève PlatformNotSupportedException, le thread de travail peut continuer et la branche catch signale cet échec synchrone ; le message est un diagnostic et ne prouve pas que le runtime ne prend en charge l’API dans tous les contextes. Dans tous les cas, n’utilisez pas la ligne Thread is abort comme preuve de l’arrêt. Après l’un ou l’autre chemin, thread.Join() attend que le thread de travail soit terminé avant le retour de Main() ; lorsque l’arrêt est pris en charge, cela signifie attendre sa fin, et lorsqu’il ne l’est pas, attendre que le thread de travail se termine normalement.
Arrêter un thread de manière coopérative avec un CancellationToken
Pour du code nouveau, arrêtez un thread de manière coopérative au lieu de le forcer. Le propriétaire du thread crée un CancellationTokenSource et passe son Token au worker. Le worker vérifie le token à des points sûrs entre les unités de travail et, lorsqu’il voit une demande d’annulation, il cesse de commencer un nouveau travail, libère les ressources qu’il possède et retourne. Le propriétaire demande l’arrêt avec Cancel(), puis appelle Join() pour attendre que le worker soit réellement terminé. L’annulation n’est qu’une demande : un worker qui ne vérifie jamais le token, ou qui est bloqué dans un seul long appel bloquant, ne s’arrête que lorsqu’il observe le token ; gardez donc chaque étape courte ou utilisez des API qui acceptent directement le token.
Appeler token.ThrowIfCancellationRequested() lève une OperationCanceledException lorsque l’annulation a été demandée, ce qui permet d’exécuter le nettoyage dans un bloc catch ou finally avant le retour du thread :
using System;
using System.Threading;
public class CooperativeStop {
public static void Main() {
using CancellationTokenSource cts = new CancellationTokenSource();
Thread worker = new Thread(() => DoWork(cts.Token));
worker.Start();
Thread.Sleep(55);
cts.Cancel();
worker.Join();
Console.WriteLine("Main: worker has stopped.");
}
private static void DoWork(CancellationToken token) {
int completed = 0;
try {
while (true) {
token.ThrowIfCancellationRequested();
completed++;
Thread.Sleep(10);
}
}
catch (OperationCanceledException) {
Console.WriteLine($"Worker: cancellation noticed after {completed} steps; resources released.");
}
}
}
Worker: cancellation noticed after 6 steps; resources released.
Main: worker has stopped.
Cette sortie a été enregistrée sur .NET 10. Le nombre exact d’étapes dépend du timing, mais l’ordre non : le worker signale d’abord son nettoyage, et la ligne Main: worker has stopped. ne peut apparaître qu’après le retour de Join(), donc l’arrêt est confirmé plutôt que supposé.
Erreurs courantes lors de l’arrêt d’un thread
- Utiliser
Abort()dans du code nouveau ou sur un runtime moderne.Thread.Abort()n’est pris en charge que sur .NET Framework. Sur .NET Core et .NET 5 ou version ultérieure, il lève unePlatformNotSupportedException(le compilateur avertit aussi SYSLIB0006), donc le worker n’est pas arrêté du tout et s’exécute simplement jusqu’au bout, comme le montre le premier exemple sur .NET 10. Le code qui doit tourner sur un runtime moderne n’a aucun repliAbort()fonctionnel et a besoin de l’annulation coopérative à la place. - Demander l’annulation sans jamais vérifier le token.
Cancel()ne fait que définir un indicateur. Si la boucle du worker n’appelle jamaisThrowIfCancellationRequested()et ne lit jamaisIsCancellationRequested, rien ne se passe et le thread continue de tourner. - Prendre la demande d’arrêt pour preuve que le thread est terminé. Ni
Cancel()niAbort()n’attendent le worker. Appelez toujoursJoin()(ou observez autrement la fin du worker) avant d’utiliser ses résultats ou de libérer des objets qu’il touche encore. - Interrompre un worker au milieu d’une mise à jour. Sur les runtimes où
Abort()fonctionne, l’exception asynchrone peut arriver pendant que le worker détient un verrou ou a mis à jour des données partagées à moitié, laissant l’état incohérent pour tous les autres threads. L’annulation coopérative évite cela parce que le worker choisit lui-même le point sûr où s’arrêter.