NET définit la fonctionnalité multithreading dans l'espace de noms System.Threading. Par conséquent, pour utiliser le multithreading, vous devez d'abord déclarer une référence à cet espace de noms (en utilisant System.Threading;).
a. Démarrez un fil de discussion. Comme son nom l'indique, « démarrer un fil de discussion » signifie créer et démarrer un fil de discussion. Le code suivant peut y parvenir :
Thread thread1 = nouveau Thread(nouveau ThreadStart(Count));
Où Count est la fonction à exécuter par le nouveau thread.
b. Tuez le fil
"Tuer un thread" consiste à éradiquer un thread. Afin de ne pas gaspiller vos efforts, il est préférable de déterminer s'il est toujours vivant (via l'attribut IsAlive) avant de tuer un thread, puis d'appeler la méthode Abort pour tuer le thread. .
c. Mettre le thread en pause signifie laisser un thread en cours d'exécution dormir pendant un certain temps. Par exemple, thread.Sleep(1000); consiste à laisser le thread dormir pendant 1 seconde.
d. La priorité ne nécessite aucune explication. L'attribut hreadPriority de la classe Thread est utilisé pour définir la priorité, mais rien ne garantit que le système d'exploitation acceptera la priorité. La priorité d'un thread peut être divisée en 5 types : Normal, AboveNormal, BelowNormal, Highest et Lowest. Des exemples de mise en œuvre spécifiques sont les suivants :
thread.Priority = ThreadPriority.Highest;
e. Suspendre le fil
La méthode Suspend de la classe Thread est utilisée pour suspendre le thread jusqu'à ce que Resume soit appelé avant que le thread puisse continuer à s'exécuter. Si le fil est déjà suspendu, cela ne fonctionnera pas.
si (thread.ThreadState = ThreadState.Running)
{
thread.Suspend();
}
f. Le thread de récupération est utilisé pour reprendre le thread suspendu afin qu'il puisse continuer à s'exécuter. Si le thread n'est pas suspendu, il ne fonctionnera pas.
si (thread.ThreadState = ThreadState.Suspended)
{
thread.Resume();
}
Un exemple est répertorié ci-dessous pour illustrer une fonctionnalité de thread simple. Cet exemple provient de la documentation d'aide.
utiliser le système ;
en utilisant System.Threading ;
// Scénario de threading simple : démarrez une méthode statique en cours d'exécution
// sur un deuxième thread.
classe publique ThreadExample {
// La méthode ThreadProc est appelée au démarrage du thread.
// Il boucle dix fois, écrit sur la console et donne
// le reste de sa tranche de temps à chaque fois, puis se termine.
public static void ThreadProc() {
pour (int je = 0; je < 10; i++) {
Console.WriteLine("ThreadProc : {0}", i);
// Rendement le reste de la tranche de temps.
Thread.Sleep(0);
}
}
public static void Main() {
Console.WriteLine("Thread principal : démarrer un deuxième thread.");
// Le constructeur de la classe Thread nécessite un ThreadStart
// délégué qui représente la méthode à exécuter sur le
// thread C# simplifie la création de ce délégué.
Fil t = nouveau fil (nouveau ThreadStart (ThreadProc));
// Démarrez ThreadProc. Sur un monoprocesseur, le thread n'obtient pas
// n'importe quel temps processeur jusqu'à ce que le thread principal cède.
// le Thread.Sleep qui suit t.Start() pour voir la différence.
t.Start();
//Thread.Sleep(0);
pour (int je = 0; je < 4; i++) {
Console.WriteLine("Thème principal : Faire du travail.");
Thread.Sleep(0);
}
Console.WriteLine("Thread principal : appelez Join() pour attendre la fin de ThreadProc.");
t.Join();
Console.WriteLine("Thread principal : ThreadProc.Join est revenu. Appuyez sur Entrée pour terminer le programme.");
Console.ReadLine();
}
}
Ce code produit une sortie similaire à celle-ci :
Fil de discussion principal : démarrez un deuxième fil de discussion.
Fil conducteur : Faites du travail.
Processus de discussion : 0
Fil conducteur : Faites du travail.
Processus de discussion : 1
Fil conducteur : Faites du travail.
ThreadProc : 2
Fil conducteur : Faites du travail.
Processus de discussion : 3
Thread principal : appelez Join() pour attendre la fin de ThreadProc.
Processus de discussion : 4
Processus de discussion : 5
Processus de discussion : 6
Processus de discussion : 7
Processus de discussion : 8
Processus de discussion : 9
Thread principal : ThreadProc.Join est revenu. Appuyez sur Entrée pour terminer le programme.
Dans Visul C#, l'espace de noms System.Threading fournit certaines classes et interfaces qui permettent la programmation multithread. Il existe trois méthodes pour créer des threads : Thread, ThreadPool et Timer. Voici une brève introduction sur la façon de les utiliser un par un.
1. Fil de discussion
Il s’agit peut-être de la méthode la plus complexe, mais elle offre une variété de contrôles flexibles sur les threads. Tout d'abord, vous devez utiliser son constructeur pour créer une instance de thread. Ses paramètres sont relativement simples, avec un seul délégué ThreadStart : public Thread(ThreadStart start); puis appelez Start() pour le démarrer. Bien sûr, vous pouvez utiliser son attribut Priority. pour définir ou obtenir sa priorité d'exécution (enum ThreadPriority : Normal, Lowest, Highest, BelowNormal, AboveNormal).
L'exemple suivant génère d'abord deux instances de thread t1 et t2, puis définit respectivement leurs priorités, puis démarre les deux threads (les deux threads sont fondamentalement les mêmes, sauf que leur sortie est différente, t1 est "1" et t2 est "2". ", Selon le ratio du nombre de caractères qu'ils produisent, on peut voir grossièrement le ratio de temps CPU qu'ils occupent, qui reflète également leurs priorités respectives).
static void Main(string[] arguments)
{
Fil t1 = nouveau fil (nouveau ThreadStart (Thread1));
Fil t2 = nouveau fil (nouveau ThreadStart (Thread2));
t1.Priority = ThreadPriority.BelowNormal ;
t2.Priority = ThreadPriority.Lowest;
t1.Start();
t2.Start();
}
public static void Thread1()
{
pour (int i = 1; i < 1000; i++)
{//Écrivez un "1" à chaque fois qu'une boucle est exécutée.
dosth();
Console.Write("1");
}
}
public statique vide Thread2()
{
pour (int i = 0; i < 1000; i++)
{//Écrivez un "2" à chaque fois qu'une boucle est exécutée.
dosth();
Console.Write("2");
}
}
public statique vide dosth()
{//Utilisé pour simuler des opérations complexes
pour (int j = 0; j < 10000000; j++)
{
entier a=15 ;
une = une*une*une*une;
}
}
Le résultat de l'exécution du programme ci-dessus est :
111111111111111111111111111111111111111112111111111111111111111111111111111111111112
111111111111111111111111111111111111111112111111111111111111111111111111111111111112
111111111111111111111111111111111111111112111111111111111111111111111111111111111112
D'après les résultats ci-dessus, nous pouvons voir que le thread t1 prend beaucoup plus de temps CPU que t2. En effet, la priorité de t1 est supérieure à celle de t2. Si nous définissons les priorités de t1 et t2 sur Normal, les résultats sont obtenus. sont comme l'image suivante :
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
À partir de l'exemple ci-dessus, nous pouvons voir que sa structure est similaire à celle du thread de travail win32, mais elle est plus simple. Il vous suffit d'utiliser la fonction à appeler par le thread en tant que délégué, puis d'utiliser le délégué comme paramètre pour. construire une instance de thread. Lorsque Start() est appelé pour démarrer, la fonction correspondante sera appelée et l'exécution commencera à partir de la première ligne de cette fonction.
Ensuite, nous combinons la propriété ThreadState du thread pour comprendre le contrôle du thread. ThreadState est un type d'énumération qui reflète l'état du thread. Lorsqu'une instance de Thread est créée pour la première fois, son ThreadState est Unstarted ; lorsque le thread est démarré en appelant Start(), son ThreadState est Running après le démarrage du thread, si vous souhaitez qu'il soit mis en pause (bloqué), vous pouvez appeler Thread. Méthode Sleep(), elle a deux méthodes surchargées (Sleep(int), Sleep(Timespan)), qui sont simplement des formats différents pour représenter la durée. Lorsque cette fonction est appelée dans un thread, cela signifie que le thread bloquera. pendant un certain temps (le temps est déterminé par le nombre de millisecondes ou Timespan passé à Sleep, mais si le paramètre est 0, cela signifie suspendre ce thread pour permettre à d'autres threads de s'exécuter, en spécifiant Infinite pour bloquer le thread indéfiniment), à cette fois, son ThreadState deviendra WaitSleepJoin. Une autre chose à noter est que la fonction Sleep() est définie comme statique ? ! Cela signifie également qu'il ne peut pas être utilisé conjointement avec une instance de thread, c'est-à-dire qu'il n'y a pas d'appel similaire à t1.Sleep(10) ! De la même manière, la fonction Sleep() ne peut être appelée que par le thread qui a besoin de « sommeil » lui-même, et n'est pas autorisée à être appelée par d'autres threads, tout comme le moment où dormir est une question personnelle qui ne peut pas être décidée par d'autres. . Mais lorsqu'un thread est dans l'état WaitSleepJoin et doit le réveiller, vous pouvez utiliser la méthode Thread.Interrupt, qui lancera une ThreadInterruptedException sur le thread. Regardons d'abord un exemple (notez la méthode appelante de Sleep) :
static void Main(string[] arguments)
{
Fil t1 = nouveau fil (nouveau ThreadStart (Thread1));
t1.Start();
t1.Interruption();
E.WaitOne();
t1.Interruption();
t1.Join();
Console.WriteLine("t1 est la fin");
}
statique AutoResetEvent E = new AutoResetEvent(false);
public static void Thread1()
{
essayer
{//On peut voir d'après les paramètres que cela provoquera l'hibernation
Thread.Sleep(Timeout.Infinite);
}
catch (System.Threading.ThreadInterruptedException e)
{//Gestionnaire d'interruption
Console.WriteLine (" 1ère interruption");
}
E.Set();
essayer
{// dormir
Thread.Sleep(Timeout.Infinite);
}
catch (System.Threading.ThreadInterruptedException e)
{
Console.WriteLine (" 2ème interruption");
}//Pause de 10 secondes
Thread.Sommeil (10000);
}
Le résultat en cours d'exécution est : 1ère interruption
2ème interruption
(Après 10 s) t1 est la fin
À partir de l'exemple ci-dessus, nous pouvons voir que la méthode Thread.Interrupt peut sortir le programme d'un état de blocage (WaitSleepJoin) et entrer dans le gestionnaire d'interruption correspondant, puis continuer l'exécution (son ThreadState passe également à Running L'utilisation de ceci). la fonction doit noter les points suivants :
1. Cette méthode peut non seulement réveiller le blocage causé par Sleep, mais est également efficace pour toutes les méthodes qui peuvent faire entrer le thread dans l'état WaitSleepJoin (telles que Wait and Join). Comme le montre l'exemple ci-dessus, lorsque vous l'utilisez, vous devez placer la méthode qui provoque le blocage des threads dans le bloc try et placer le gestionnaire d'interruption correspondant dans le bloc catch.
2. Appelez Interrupt sur un certain thread. S'il est dans l'état WaitSleepJoin, il entrera dans le gestionnaire d'interruption correspondant pour exécution. S'il n'est pas dans l'état WaitSleepJoin à ce moment-là, il sera immédiatement interrompu lorsqu'il entrera dans cet état plus tard. . Si Interrupt est appelé plusieurs fois avant l'interruption, seul le premier appel est valide. C'est pourquoi j'ai utilisé la synchronisation dans l'exemple ci-dessus, afin de m'assurer que le deuxième appel à Interrupt soit appelé après la première interruption, sinon cela pourrait provoquer la seconde. L'appel n'a aucun effet (s'il est appelé avant la première interruption). Vous pouvez essayer de supprimer la synchronisation. Le résultat sera probablement : 1ère interruption.
L'exemple ci-dessus utilise également deux autres méthodes pour faire entrer le thread dans l'état WaitSleepJoin : en utilisant l'objet de synchronisation et la méthode Thread.Join. L'utilisation de la méthode Join est relativement simple. Cela signifie que le thread actuel appelant cette méthode se bloque jusqu'à ce que l'autre thread (t1 dans cet exemple) se termine ou que le temps spécifié soit écoulé (s'il prend également un paramètre time When two If any). condition (le cas échéant) se produit, il met immédiatement fin à l'état WaitSleepJoin et entre dans l'état Running (la condition peut être déterminée en fonction de la valeur de retour de la méthode .Join. Si c'est vrai, le thread est terminé ; s'il est faux, le temps est écoulé). Le thread peut également être suspendu à l'aide de la méthode Thread.Suspend Lorsqu'un thread est dans l'état Running et que la méthode Suspend est appelée sur lui, il entrera dans l'état SuspendRequested, mais il ne sera pas suspendu immédiatement jusqu'à ce que le thread atteigne un niveau sûr. Le thread se bloque, à quel point il entrera dans l'état Suspendu. S'il est appelé sur un thread déjà suspendu, il ne sera pas valide. Pour reprendre l'opération, appelez simplement Thread.Resume.
La dernière chose dont nous parlons est la destruction des threads. Nous pouvons appeler la méthode Abort sur le thread qui doit être détruit, et elle lancera une ThreadAbortException sur ce thread. Nous pouvons mettre du code dans le thread dans le bloc try et placer le code de traitement correspondant dans le bloc catch correspondant. Lorsque le thread exécute le code dans le bloc try, si Abort est appelé, il sautera dans le bloc catch correspondant. . Exécuté dans le bloc catch, il se terminera après l'exécution du code dans le bloc catch (si ResetAbort est exécuté dans le bloc catch, ce sera différent : il annulera la requête Abort en cours et continuera à s'exécuter vers le bas. Par conséquent, si vous. souhaitez vous assurer qu'un thread se termine, il est préférable d'utiliser Join , comme dans l'exemple ci-dessus).
2. Pool de discussions
Thread Pool (ThreadPool) est une méthode relativement simple. Elle convient aux tâches courtes qui nécessitent plusieurs threads (comme certains threads souvent bloqués). Son inconvénient est qu'elle ne peut pas contrôler les threads créés et sa priorité ne peut pas non plus être définie. Puisque chaque processus n'a qu'un seul pool de threads, et bien sûr chaque domaine d'application n'a qu'un seul pool de threads (ligne), vous constaterez que les fonctions membres de la classe ThreadPool sont toutes statiques ! Lorsque vous appelez ThreadPool.QueueUserWorkItem, ThreadPool.RegisterWaitForSingleObject, etc. pour la première fois, une instance de pool de threads sera créée. Ce qui suit est une introduction aux deux fonctions du pool de threads :
public static bool QueueUserWorkItem ( // Renvoie vrai si l'appel réussit
WaitCallback callBack,//Le délégué appelé par le thread à créer
état de l'objet //Paramètres transmis au délégué
)//C'est une autre fonction surchargée qui est similaire, sauf que le délégué ne prend aucun paramètre. La fonction de cette fonction est de mettre en file d'attente le thread à créer dans le pool de threads lorsque le nombre de threads disponibles dans le pool de threads n'est pas nul (. le pool de threads a créé des threads. Si le nombre est limité (la valeur par défaut est 25), ce thread sera créé. Sinon, il sera mis en file d'attente dans le pool de threads et attendra qu'il ait des threads disponibles.
public statique RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject, // WaitHandle à enregistrer
WaitOrTimerCallback callBack, // Délégué d'appel de thread
état de l'objet, // Paramètres transmis au délégué
int TimeOut,//Timeout, l'unité est en millisecondes,
bool executeOnlyOnce file:// S'il faut exécuter une seule fois
);
délégué public void WaitOrTimerCallback (
état de l'objet, // c'est-à-dire les paramètres transmis au délégué
bool timedOut // true signifie en raison d'un appel de délai d'attente, sinon cela signifie waitObject
);
La fonction de cette fonction est de créer un thread en attente. Une fois cette fonction appelée, ce thread sera créé. Il sera dans un état "bloqué" jusqu'à ce que le paramètre waitObject passe à l'état terminé ou que l'heure définie TimeOut arrive. Il convient de noter que ce "blocage" est très différent de l'état WaitSleepJoin du Thread : lorsqu'un Thread est dans l'état WaitSleepJoin, le CPU le réveillera régulièrement pour interroger et mettre à jour les informations d'état, puis entrera à nouveau dans l'état WaitSleepJoin. Le changement de thread nécessite beaucoup de ressources ; et le thread créé avec cette fonction est différent. Le processeur ne basculera pas vers ce thread avant d'être déclenché pour s'exécuter. Il ne prend pas de temps CPU et ne gaspille pas de temps de changement de thread, mais comment le CPU fonctionne-t-il. tu sais quand l'exécuter ? En fait, le pool de threads générera des threads auxiliaires pour surveiller ces conditions de déclenchement. Une fois les conditions remplies, les threads correspondants seront démarrés. Bien sûr, ces threads auxiliaires eux-mêmes prennent également du temps, mais si vous devez créer plus d'attente. threads, utilisez le pool de threads. Les avantages deviennent plus évidents. Voir exemple ci-dessous :
static AutoResetEvent ev=new AutoResetEvent(false);
public static int Main (string[] arguments)
{ ThreadPool.RegisterWaitForSingleObject(
ev,
nouveau WaitOrTimerCallback(WaitThreadFunc),
4,
2000,
false // Indique que la minuterie sera réinitialisée à chaque fois que l'opération d'attente est terminée jusqu'à la déconnexion et l'attente.
);
ThreadPool.QueueUserWorkItem (nouveau WaitCallback (ThreadFunc),8) ;
Thread.Sommeil (10000);
renvoie 0 ;
}
public static void ThreadFunc (objet b)
{ Console.WriteLine ("l'objet est {0}",b);
pour(int i=0;i<2;i++)
{ Thread.Sleep (1000);
ev.Set();
}
}
public static void WaitThreadFunc (objet b, bool t)
{ Console.WriteLine ("l'objet est {0},t est {1}",b,t);
}
Le résultat de son opération est :
l'objet est 8
l'objet est 4,t est faux
l'objet est 4,t est faux
l'objet est 4,t est vrai
l'objet est 4,t est vrai
l'objet est 4,t est vrai
D'après les résultats ci-dessus, nous pouvons voir que le thread ThreadFunc s'est exécuté une fois et que WaitThreadFunc s'est exécuté 5 fois. Nous pouvons juger de la raison du démarrage de ce fil à partir du paramètre bool t dans WaitOrTimerCallback : si t est faux, cela signifie waitObject, sinon cela est dû à un timeout. De plus, nous pouvons également transmettre certains paramètres au thread via l'objet b.
3. Minuterie
Il convient aux méthodes qui doivent être appelées périodiquement. Il ne s'exécute pas dans le thread qui a créé le timer. Il s'exécute dans un thread distinct automatiquement alloué par le système. Ceci est similaire à la méthode SetTimer dans Win32. Sa structure est :
Minuterie publique (
Rappel TimerCallback, // Méthode à appeler
état de l'objet, // paramètres passés au rappel
int dueTime,//Combien de temps faudra-t-il pour commencer à rappeler ?
int period//L'intervalle de temps pour appeler cette méthode
); // Si dueTime vaut 0, le rappel exécute son premier appel immédiatement. Si dueTime est Infinite, le rappel n'appelle pas sa méthode. La minuterie est désactivée, mais elle peut être réactivée à l'aide de la méthode Change. Si period est 0 ou Infinite et que dueTime n'est pas Infinite, le rappel appelle sa méthode une fois. Le comportement périodique du minuteur est désactivé, mais il peut être réactivé à l'aide de la méthode Change. Si period est zéro (0) ou Infinite et que dueTime n'est pas Infinite, le rappel appelle sa méthode une fois. Le comportement périodique du minuteur est désactivé, mais il peut être réactivé à l'aide de la méthode Change.
Si nous voulons modifier la période et le dueTime du timer après l'avoir créé, nous pouvons le modifier en appelant la méthode Change de Timer :
public bool Changement (
int dueTime,
période int
);//Evidemment les deux paramètres modifiés correspondent aux deux paramètres de Timer
public static int Main (string[] arguments)
{
Console.WriteLine ("la période est 1000");
Timer tm=new Timer (nouveau TimerCallback (TimerCall),3,1000,1000) ;
Fil. Sommeil (2000);
Console.WriteLine ("la période est de 500");
tm.Changement (0,800);
Thread.Sommeil (3000);
renvoie 0 ;
}
public static void TimerCall (objet b)
{
Console.WriteLine ("timercallback ; b est {0}", b);
}
Le résultat de son opération est :
la période est de 1000
timercallback;b vaut 3
timercallback;b vaut 3
la période est de 500
timercallback;b vaut 3
timercallback;b vaut 3
timercallback;b vaut 3
timercallback;b vaut 3
Résumer
À partir de la brève introduction ci-dessus, nous pouvons voir les occasions où ils sont utilisés respectivement : Thread convient aux occasions qui nécessitent un contrôle complexe des threads ; ThreadPool convient aux tâches courtes qui nécessitent plusieurs threads (comme certains qui sont souvent bloqués). ); Timer convient aux méthodes qui doivent être appelées périodiquement. Tant que nous comprenons leurs caractéristiques d'utilisation, nous pouvons bien choisir la méthode appropriée.