J'ai lu la documentation à ce sujet et je pense comprendre. Un AutoResetEvent
est réinitialisé lorsque le code passe par event.WaitOne()
, mais pas ManualResetEvent
.
Est-ce correct?
Oui. C'est comme la différence entre un péage et une porte. La ManualResetEvent
est la porte qu'il faut fermer (réinitialiser) manuellement. La AutoResetEvent
est un péage permettant à une voiture de passer et de se fermer automatiquement avant que la suivante puisse passer.
Imaginez simplement que la AutoResetEvent
exécute WaitOne()
et Reset()
comme une seule opération atomique.
La reponse courte est oui. La différence la plus importante est qu'un AutoResetEvent n'autorise qu'un seul thread en attente à continuer. En revanche, un événement ManualResetEvent permet aux threads, plusieurs à la fois, de continuer jusqu'à ce que vous leur demandiez de s'arrêter (réinitialisez-le).
Tiré du livre de synthèse C # 3.0, de Joseph Albahari
Threading in C # - Livre électronique gratuit
Un ManualResetEvent est une variante de AutoResetEvent. Il diffère en ce sens qu'il ne se réinitialise pas automatiquement après qu'un thread est passé dans un appel WaitOne, et fonctionne donc comme une porte: l'appel de Set ouvre la porte, autorisant le nombre de threads traversés par WaitOne; l'appel de Reset ferme la porte, ce qui peut éventuellement entraîner une file d'attente de serveurs jusqu'à sa prochaine ouverture.
On pourrait simuler cette fonctionnalité avec un champ booléen "gateOpen" (déclaré avec le mot clé volatile) en combinaison avec "spin-sleep" - vérifiant le drapeau de manière répétée, puis dormant pendant une courte période.
ManualResetEvents sont parfois utilisés pour signaler qu'une opération particulière est terminée ou que l'initialisation terminée d'un thread est prête à être exécutée.
J'ai créé des exemples simples pour clarifier la compréhension de ManualResetEvent
vs AutoResetEvent
.
AutoResetEvent
: supposons que vous ayez 3 threads de production. Si l'un de ces threads appelle WaitOne()
, tous les 2 autres threads arrêteront l'exécution et attendront le signal. Je suppose qu'ils utilisent WaitOne()
. C'est comme; si je ne travaille pas, personne ne travaille. Dans le premier exemple, vous pouvez voir que
autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();
Lorsque vous appelez Set()
, tous les threads fonctionnent et attendent le signal. Après 1 seconde, j'envoie un deuxième signal et ils s'exécutent et attendent (WaitOne()
). Pensez à ces gars-là sont des joueurs de l'équipe de football et si un joueur dit j'attendrai jusqu'à ce que le gestionnaire m'appelle, et d'autres attendent jusqu'à ce que le gestionnaire leur dise de continuer (Set()
)
public class AutoResetEventSample
{
private AutoResetEvent autoReset = new AutoResetEvent(false);
public void RunAll()
{
new Thread(Worker1).Start();
new Thread(Worker2).Start();
new Thread(Worker3).Start();
autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();
Console.WriteLine("Main thread reached to end.");
}
public void Worker1()
{
Console.WriteLine("Entered in worker 1");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker1 is running {0}", i);
Thread.Sleep(2000);
autoReset.WaitOne();
}
}
public void Worker2()
{
Console.WriteLine("Entered in worker 2");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker2 is running {0}", i);
Thread.Sleep(2000);
autoReset.WaitOne();
}
}
public void Worker3()
{
Console.WriteLine("Entered in worker 3");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker3 is running {0}", i);
Thread.Sleep(2000);
autoReset.WaitOne();
}
}
}
Dans cet exemple, vous pouvez clairement voir que lorsque vous frappez pour la première fois Set()
, tous les threads seront lâchés, puis après 1 seconde, il est signalé à tous les threads d'attendre! Dès que vous les définissez à nouveau, peu importe qu'ils appellent WaitOne()
à l'intérieur, ils continuent à fonctionner car vous devez appeler manuellement Reset()
pour les arrêter tous.
manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();
La relation Arbitre/Joueurs y est plus importante indépendamment du fait que le joueur soit blessé et attendre que les autres joueurs continuent à travailler. Si l'arbitre dit d'attendre (Reset()
), tous les joueurs attendent le prochain signal.
public class ManualResetEventSample
{
private ManualResetEvent manualReset = new ManualResetEvent(false);
public void RunAll()
{
new Thread(Worker1).Start();
new Thread(Worker2).Start();
new Thread(Worker3).Start();
manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();
Console.WriteLine("Main thread reached to end.");
}
public void Worker1()
{
Console.WriteLine("Entered in worker 1");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker1 is running {0}", i);
Thread.Sleep(2000);
manualReset.WaitOne();
}
}
public void Worker2()
{
Console.WriteLine("Entered in worker 2");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker2 is running {0}", i);
Thread.Sleep(2000);
manualReset.WaitOne();
}
}
public void Worker3()
{
Console.WriteLine("Entered in worker 3");
for (int i = 0; i < 5; i++) {
Console.WriteLine("Worker3 is running {0}", i);
Thread.Sleep(2000);
manualReset.WaitOne();
}
}
}
autoResetEvent.WaitOne()
est similaire à
try
{
manualResetEvent.WaitOne();
}
finally
{
manualResetEvent.Reset();
}
comme une opération atomique
Bien, normalement il n’est pas recommandé d’ajouter 2 réponses dans le même fil, mais je ne voulais pas éditer/supprimer ma réponse précédente, car cela pourrait aider d’une autre manière.
À présent, j'ai créé un extrait d'application d'application de la console d'exécution run-to-learn, beaucoup plus complet et facile à comprendre, ci-dessous.
Il suffit d’exécuter les exemples sur deux consoles différentes et d’observer le comportement. Vous aurez une idée beaucoup plus claire de ce qui se passe dans les coulisses.
Evénement de réinitialisation manuelle
using System;
using System.Threading;
namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
public class ManualResetEventSample
{
private readonly ManualResetEvent _manualReset = new ManualResetEvent(false);
public void RunAll()
{
new Thread(Worker1).Start();
new Thread(Worker2).Start();
new Thread(Worker3).Start();
Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
Thread.Sleep(15000);
Console.WriteLine("1- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_manualReset.Set();
Thread.Sleep(2000);
Console.WriteLine("2- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_manualReset.Set();
Thread.Sleep(2000);
Console.WriteLine("3- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_manualReset.Set();
Thread.Sleep(2000);
Console.WriteLine("4- Main will call ManualResetEvent.Reset() in 5 seconds, watch out!");
Thread.Sleep(5000);
_manualReset.Reset();
Thread.Sleep(2000);
Console.WriteLine("It ran one more time. Why? Even Reset Sets the state of the event to nonsignaled (false), causing threads to block, this will initial the state, and threads will run again until they WaitOne().");
Thread.Sleep(10000);
Console.WriteLine();
Console.WriteLine("This will go so on. Everytime you call Set(), ManualResetEvent will let ALL threads to run. So if you want synchronization between them, consider using AutoReset event, or simply user TPL (Task Parallel Library).");
Thread.Sleep(5000);
Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker1()
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("Worker1 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
// this gets blocked until _autoReset gets signal
_manualReset.WaitOne();
}
Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker2()
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("Worker2 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
// this gets blocked until _autoReset gets signal
_manualReset.WaitOne();
}
Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker3()
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("Worker3 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
// this gets blocked until _autoReset gets signal
_manualReset.WaitOne();
}
Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
}
}
Evénement de réinitialisation automatique
using System;
using System.Threading;
namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
public class AutoResetEventSample
{
private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);
public void RunAll()
{
new Thread(Worker1).Start();
new Thread(Worker2).Start();
new Thread(Worker3).Start();
Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
Thread.Sleep(15000);
Console.WriteLine("1- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_autoReset.Set();
Thread.Sleep(2000);
Console.WriteLine("2- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_autoReset.Set();
Thread.Sleep(2000);
Console.WriteLine("3- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
Thread.Sleep(5000);
_autoReset.Set();
Thread.Sleep(2000);
Console.WriteLine("4- Main will call AutoResetEvent.Reset() in 5 seconds, watch out!");
Thread.Sleep(5000);
_autoReset.Reset();
Thread.Sleep(2000);
Console.WriteLine("Nothing happened. Why? Becasuse Reset Sets the state of the event to nonsignaled, causing threads to block. Since they are already blocked, it will not affect anything.");
Thread.Sleep(10000);
Console.WriteLine("This will go so on. Everytime you call Set(), AutoResetEvent will let another thread to run. It will make it automatically, so you do not need to worry about thread running order, unless you want it manually!");
Thread.Sleep(5000);
Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker1()
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine("Worker1 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
// this gets blocked until _autoReset gets signal
_autoReset.WaitOne();
}
Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker2()
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine("Worker2 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
// this gets blocked until _autoReset gets signal
_autoReset.WaitOne();
}
Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
public void Worker3()
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine("Worker3 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
// this gets blocked until _autoReset gets signal
_autoReset.WaitOne();
}
Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
}
}
}
Oui. C'est absolument correct.
Vous pouvez voir ManualResetEvent comme un moyen d'indiquer l'état. Quelque chose est allumé (réglé) ou éteint (réinitialisé). Un événement d'une certaine durée. Tout thread en attente de cet état peut continuer.
Un AutoResetEvent est plus comparable à un signal. Une indication unique que quelque chose est arrivé. Une occurrence sans durée. En règle générale, mais pas nécessairement, "quelque chose" qui s'est passé est petit et doit être géré par un seul thread - par conséquent, la réinitialisation automatique après qu'un seul thread a consommé l'événement.
AutoResetEvent conserve une variable booléenne en mémoire. Si la variable booléenne est fausse, elle bloque le thread et si la variable booléenne est vraie, elle débloque le thread.
Lorsque nous instancions un objet AutoResetEvent, nous passons la valeur par défaut de valeur booléenne dans le constructeur. Vous trouverez ci-dessous la syntaxe d'instancier un objet AutoResetEvent.
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
méthode WaitOne
Cette méthode bloque le thread actuel et attend le signal par un autre thread. La méthode WaitOne place le thread en cours dans un état de thread en veille. La méthode WaitOne renvoie true si elle reçoit le signal, sinon elle renvoie false.
autoResetEvent.WaitOne();
La seconde surcharge de la méthode WaitOne attend le nombre de secondes spécifié. S'il n'obtient aucun signal, le thread continue son travail.
static void ThreadMethod()
{
while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Continue");
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("Thread got signal");
}
Nous avons appelé la méthode WaitOne en passant les 2 secondes comme arguments. Dans la boucle while, il attend le signal pendant 2 secondes, puis continue son travail. Lorsque le thread a reçu le signal, WaitOne renvoie la valeur true, quitte la boucle et affiche le "Signal obtenu par le thread".
Méthode Set
La méthode AutoResetEvent Set a envoyé le signal au thread en attente pour poursuivre son travail. Ci-dessous la syntaxe d'appeler la méthode Set.
autoResetEvent.Set();
ManualResetEvent conserve une variable booléenne en mémoire. Lorsque la variable booléenne est false, elle bloque tous les threads et lorsque la variable booléenne est vraie, elle débloque tous les threads.
Lorsque nous instancions un ManualResetEvent, nous l'initialisons avec une valeur booléenne par défaut.
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Dans le code ci-dessus, nous initialisons ManualResetEvent avec une valeur false, ce qui signifie que tous les threads qui appellent la méthode WaitOne bloquent jusqu'à ce qu'un thread appelle la méthode Set ().
Si nous initialisons ManualResetEvent avec une valeur true, tous les threads qui appellent la méthode WaitOne ne se bloqueront pas et seront libres de continuer.
Méthode WaitOne
Cette méthode bloque le thread actuel et attend le signal par un autre thread. Il retourne vrai s'il reçoit un signal, sinon retourne faux.
Vous trouverez ci-dessous la syntaxe d'appel de la méthode WaitOne.
manualResetEvent.WaitOne();
Dans la seconde surcharge de la méthode WaitOne, nous pouvons spécifier l'intervalle de temps jusqu'à ce que le thread actuel attend le signal. Si, dans le temps, interne, il ne reçoit pas de signal, il retourne false et passe à la ligne suivante de la méthode.
Vous trouverez ci-dessous la syntaxe d'appeler la méthode WaitOne avec un intervalle de temps.
bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
Nous avons spécifié 5 secondes dans la méthode WaitOne. Si l'objet manualResetEvent ne reçoit pas de signal entre 5 secondes, il affecte la valeur false à la variable isSignalled.
Définir la méthode
Cette méthode est utilisée pour envoyer le signal à tous les threads en attente. Méthode Set () définit la variable booléenne de l'objet ManualResetEvent sur true. Tous les threads en attente sont débloqués et continuent plus loin.
Vous trouverez ci-dessous la syntaxe d'appel de la méthode Set ().
manualResetEvent.Set();
Méthode de réinitialisation
Une fois que nous avons appelé la méthode Set () sur l'objet ManualResetEvent, sa valeur booléenne reste vraie. Pour réinitialiser la valeur, nous pouvons utiliser la méthode Reset (). Méthode de réinitialisation change la valeur booléenne en faux.
Vous trouverez ci-dessous la syntaxe d'appeler la méthode Reset.
manualResetEvent.Reset();
Nous devons immédiatement appeler la méthode Reset après avoir appelé la méthode Set si nous voulons envoyer le signal aux threads plusieurs fois.
Oui c'est vrai.
Vous pouvez avoir une idée par l'utilisation de ces deux.
Si vous avez besoin de savoir que vous avez terminé avec un travail et que d'autres (tâches) en attente peuvent continuer, vous devez utiliser ManualResetEvent.
Si vous avez besoin d'un accès exclusif mutuel à une ressource, vous devez utiliser AutoResetEvent.
Si vous voulez comprendre AutoResetEvent et ManualResetEvent, vous devez comprendre non pas le threading, mais les interruptions!
.NET veut évoquer une programmation de bas niveau la plus éloignée possible.
Une interruption est quelque chose utilisé dans la programmation de bas niveau qui équivaut à un signal qui devient faible (ou vice-versa). Lorsque cela se produit, le programme interrompt son exécution normale et déplace le pointeur d'exécution vers la fonction qui gère cet événement .
La première chose à faire quand une interruption survient est de réinitialiser son état, car le matériel fonctionne de cette manière:
C'est la différence entre ManualResetEvent et AutoResetEvent.
Si un événement ManualResetEvent se produit et que je ne le réinitialise pas, la prochaine fois que cela se produira, je ne serai pas en mesure de l'écouter.