Maîtriser le multithreading C# : Améliorer les performances et l'efficacité
Un thread (fil d'exécution) est défini comme le chemin d'exécution d'un programme. Chaque thread définit un flux de contrôle unique. Si votre application implique des opérations complexes et chronophages, il est souvent utile de définir différents chemins d'exécution ou threads, chaque thread effectuant un travail particulier.
Les threads sont des processus légers. Un exemple courant d'utilisation de thread est l'implémentation de la programmation concurrente par les systèmes d'exploitation modernes. L'utilisation de threads évite le gaspillage de cycles CPU et augmente l'efficacité d'une application.
Jusqu'à présent, nous avons écrit des programmes où un seul thread s'exécute comme un processus unique, qui est l'instance en cours d'exécution de l'application. Cependant, de cette façon, l'application ne peut effectuer qu'un seul travail à la fois. Pour la faire exécuter plus d'une tâche à la fois, elle peut être divisée en threads plus petits.
Cycle de vie d'un thread
Le cycle de vie d'un thread commence lorsque qu'un objet de la classe System.Threading.Thread est créé et se termine lorsque le thread est terminé ou termine son exécution.
Voici les différents états dans le cycle de vie d'un thread :
État Non démarré : C'est la situation lorsque l'instance du thread est créée mais que la méthode Start n'est pas appelée.
État Prêt : C'est la situation lorsque le thread est prêt à s'exécuter et attend un cycle CPU.
État Non exécutable : Un thread n'est pas exécutable lorsque :
- La méthode Sleep a été appelée
- La méthode Wait a été appelée
- Bloqué par des opérations d'entrée/sortie
État Mort : C'est la situation lorsque le thread termine son exécution ou est avorté.
Le thread principal
En C#, la classe System.Threading.Thread est utilisée pour travailler avec les threads. Elle permet de créer et d'accéder aux threads individuels dans une application multithread. Le premier thread à être exécuté dans un processus est appelé le thread principal (main thread).
Lorsqu'un programme C# commence son exécution, le thread principal est créé automatiquement. Les threads créés à l'aide de la classe Thread sont appelés les threads enfants du thread principal. Vous pouvez accéder à un thread en utilisant la propriété CurrentThread de la classe Thread.
Le programme suivant montre l'exécution du thread principal :
Démonstration en directusing System;
using System.Threading;
namespace MultithreadingApplication {
class MainThreadProgram {
static void Main(string[] args) {
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine("Ceci est {0}", th.Name);
Console.ReadKey();
}
}
}
Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant :
Ceci est MainThread
Propriétés et méthodes de la classe Thread
Le tableau suivant présente les propriétés les plus couramment utilisées de la classe Thread :
| N° | Propriété et description |
|---|---|
| 1 | CurrentContext Obtient le contexte actuel dans lequel le thread s'exécute. |
| 2 | CurrentCulture Obtient ou définit la culture pour le thread actuel. |
| 3 | CurrentPrincipal Obtient ou définit le principal actuel du thread (pour la sécurité basée sur les rôles). |
| 4 | CurrentThread Obtient le thread en cours d'exécution. |
| 5 | CurrentUICulture Obtient ou définit la culture actuelle utilisée par le Resource Manager pour rechercher les ressources spécifiques à la culture à l'exécution. |
| 6 | ExecutionContext Obtient un objet ExecutionContext contenant des informations sur les différents contextes du thread actuel. |
| 7 | IsAlive Obtient une valeur indiquant l'état d'exécution du thread actuel. |
| 8 | IsBackground Obtient ou définit une valeur indiquant si un thread est un thread d'arrière-plan. |
| 9 | IsThreadPoolThread Obtient une valeur indiquant si un thread appartient au pool de threads managés. |
| 10 | ManagedThreadId Obtient un identificateur unique pour le thread managé actuel. |
| 11 | Name Obtient ou définit le nom du thread. |
| 12 | Priority Obtient ou définit une valeur indiquant la priorité de planification d'un thread. |
| 13 | ThreadState Obtient une valeur contenant les états du thread actuel. |
Le tableau suivant présente les méthodes les plus couramment utilisées de la classe Thread :
| N° | Méthode et description |
|---|---|
| 1 | public void Abort() Lève une ThreadAbortException dans le thread sur lequel elle est invoquée, pour commencer le processus de terminaison du thread. L'appel de cette méthode termine généralement le thread. |
| 2 | public static LocalDataStoreSlot AllocateDataSlot() Alloue un emplacement de données non nommé sur tous les threads. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 3 | public static LocalDataStoreSlot AllocateNamedDataSlot(string name) Alloue un emplacement de données nommé sur tous les threads. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 4 | public static void BeginCriticalRegion() Notifie un hôte que l'exécution est sur le point d'entrer dans une région de code où les effets d'un abandon de thread ou d'une exception non gérée pourraient compromettre d'autres tâches dans le domaine d'application. |
| 5 | public static void BeginThreadAffinity() Notifie un hôte que le code managé est sur le point d'exécuter des instructions qui dépendent de l'identité du thread physique du système d'exploitation actuel. |
| 6 | public static void EndCriticalRegion() Notifie un hôte que l'exécution est sur le point d'entrer dans une région de code où les effets d'un abandon de thread ou d'une exception non gérée sont limités à la tâche courante. |
| 7 | public static void EndThreadAffinity() Notifie un hôte que le code managé a fini d'exécuter des instructions qui dépendent de l'identité du thread physique du système d'exploitation actuel. |
| 8 | public static void FreeNamedDataSlot(string name) Élimine l'association entre un nom et un emplacement, pour tous les threads du processus. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 9 | public static Object GetData(LocalDataStoreSlot slot) Récupère la valeur de l'emplacement spécifié sur le thread actuel, dans le domaine actuel du thread. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 10 | public static AppDomain GetDomain() Retourne le domaine actuel dans lequel le thread actuel s'exécute. |
| 11 | public static AppDomain GetDomainID() Retourne un identifiant unique de domaine d'application. |
| 12 | public static LocalDataStoreSlot GetNamedDataSlot(string name) Recherche un emplacement de données nommé. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 13 | public void Interrupt() Interrompt un thread qui est dans l'état WaitSleepJoin. |
| 14 | public void Join() Bloque le thread appelant jusqu'à ce qu'un thread se termine, tout en continuant à effectuer le pompage standard COM et SendMessage. Cette méthode a différentes formes surchargées. |
| 15 | public static void MemoryBarrier() Synchronise l'accès à la mémoire comme suit : Le processeur exécutant le thread actuel ne peut pas réorganiser les instructions de manière à ce que les accès mémoire avant l'appel à MemoryBarrier s'exécutent après les accès mémoire qui suivent l'appel. |
| 16 | public static void ResetAbort() Annule un Abort demandé pour le thread actuel. |
| 17 | public static void SetData(LocalDataStoreSlot slot, Object data) Définit les données dans l'emplacement spécifié sur le thread en cours d'exécution, pour le domaine actuel de ce thread. Pour de meilleures performances, utilisez des champs marqués avec l'attribut ThreadStaticAttribute à la place. |
| 18 | public void Start() Démarre un thread. |
| 19 | public static void Sleep(int millisecondsTimeout) Met le thread en pause pendant une période de temps. |
| 20 | public static void SpinWait(int iterations) Fait attendre un thread le nombre de fois défini par le paramètre iterations. |
| 21 | public static byte VolatileRead(ref byte address) public static double VolatileRead(ref double address) public static int VolatileRead(ref int address) public static Object VolatileRead(ref Object address) Lit la valeur d'un champ. La valeur est la plus récente écrite par n'importe quel processeur dans un ordinateur, quel que soit le nombre de processeurs ou l'état du cache du processeur. Cette méthode a différentes formes surchargées. Seules certaines sont données ci-dessus. |
| 22 | public static void VolatileWrite(ref byte address, byte value) public static void VolatileWrite(ref double address, double value) public static void VolatileWrite(ref int address, int value) public static void VolatileWrite(ref Object address, Object value) Écrit une valeur dans un champ immédiatement, de sorte que la valeur soit visible pour tous les processeurs de l'ordinateur. Cette méthode a différentes formes surchargées. Seules certaines sont données ci-dessus. |
| 23 | public static bool Yield() Force le thread appelant à céder l'exécution à un autre thread prêt à s'exécuter sur le processeur actuel. Le système d'exploitation sélectionne le thread auquel céder. |
Création de threads
Les threads sont créés en étendant la classe Thread. La classe Thread étendue appelle ensuite la méthode Start() pour commencer l'exécution du thread enfant.
Le programme suivant démontre le concept :
Démonstration en directusing System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
Console.WriteLine("Le thread enfant démarre");
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("Dans Main : Création du thread enfant");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}
Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant :
Dans Main : Création du thread enfant Le thread enfant démarre
Gestion des threads
La classe Thread fournit diverses méthodes pour gérer les threads.
L'exemple suivant montre l'utilisation de la méthode Sleep() pour mettre un thread en pause pendant une période spécifique.
Démonstration en directusing System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
Console.WriteLine("Le thread enfant démarre");
// Le thread est mis en pause pendant 5000 millisecondes
int sleepfor = 5000;
Console.WriteLine("Thread enfant en pause pour {0} secondes", sleepfor / 1000);
Thread.Sleep(sleepfor);
Console.WriteLine("Le thread enfant reprend");
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("Dans Main : Création du thread enfant");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}
Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant :
Dans Main : Création du thread enfant Le thread enfant démarre Thread enfant en pause pour 5 secondes Le thread enfant reprend
Destruction des threads
La méthode Abort() est utilisée pour détruire les threads.
Le runtime termine le thread en levant une ThreadAbortException. Cette exception ne peut pas être interceptée, le contrôle est envoyé au bloc finally, s'il y en a un.
Le programme suivant illustre cela :
Démonstration en directusing System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
try {
Console.WriteLine("Le thread enfant démarre");
// Faire un travail, comme compter jusqu'à 10
for (int counter = 0; counter <= 10; counter++) {
Thread.Sleep(500);
Console.WriteLine(counter);
}
Console.WriteLine("Thread enfant terminé");
} catch (ThreadAbortException e) {
Console.WriteLine("Exception d'abandon de thread");
} finally {
Console.WriteLine("Impossible d'intercepter l'exception de thread");
}
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("Dans Main : Création du thread enfant");
Thread childThread = new Thread(childref);
childThread.Start();
// Arrêter le thread principal pendant un certain temps
Thread.Sleep(2000);
// Maintenant, abandonner le thread enfant
Console.WriteLine("Dans Main : Abandon du thread enfant");
childThread.Abort();
Console.ReadKey();
}
}
}
Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant :
Dans Main : Création du thread enfant Le thread enfant démarre 0 1 2 Dans Main : Abandon du thread enfant Exception d'abandon de thread Impossible d'intercepter l'exception de thread
Langue C
- Diamètres de filet mineur vs majeur : comment choisir la bonne taille de vis
- Le fil à coudre : types, histoire, production et contrôle qualité
- Thread vs ZigBee : Comparatif technique pour ingénieurs IoT
- Maîtriser le multithreading en C++ : Programmation concurrente efficace avec POSIX
- Maîtriser la programmation multithread en Python : Techniques de concurrence efficaces
- L'Edge Computing obtient un cadre clair, approuvé par l'industrie
- Illumination arrière rapide : capturer plus d'un million d'images par seconde
- Démêler le fil numérique : le plan de Siemens PLM pour la fabrication intelligente
- Comprendre les coupe-fils : types, utilisations et applications