Quelle est la différence entre ManualResetEvent et AutoResetEvent dans .NET?
-
03-07-2019 - |
Question
J'ai lu la documentation à ce sujet et je pense comprendre. Un AutoResetEvent
est réinitialisé lorsque le code passe par event.WaitOne ()
, mais un ManualResetEvent
ne le fait pas.
Est-ce correct?
La solution
Oui. C'est comme la différence entre un péage et une porte. Le ManualResetEvent
est la porte qui doit être fermée manuellement (réinitialisation). AutoResetEvent
est un péage permettant à une voiture de passer et se fermant automatiquement avant que la suivante ne puisse passer.
Autres conseils
Imaginez que AutoResetEvent
exécute WaitOne ()
et Reset ()
en une seule opération atomique.
La réponse 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).
Extrait du livre C # 3.0 Nutshell, de Joseph Albahari
Threading en 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: appelant Set ouvre la porte, permettant à n'importe quel nombre de threads passant par WaitOne à la porte; l'appel de Reset ferme la porte, ce qui peut éventuellement entraîner l'accumulation d'une file d'attente de serveurs jusqu'à la prochaine ouverture.
On pourrait simuler cette fonctionnalité avec un booléen "gateOpen". champ (déclaré avec le mot clé volatile) en combinaison avec " spin-sleep " & # 8211; en vérifiant à plusieurs reprises le drapeau, puis en 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 d'un thread est terminée et qu'elle est prête à exécuter le travail.
J'ai créé des exemples simples pour clarifier la compréhension de ManualResetEvent
par rapport à 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à qui sont des joueurs d’équipe de football et si un joueur dit que j’attendrai que le responsable m’appelle, les autres attendent jusqu’à ce que le responsable 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 voir clairement que lorsque vous frappez Set ()
pour la première fois, tous les threads disparaissent, 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 ()
, ils continuent de 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();
Il s’agit davantage de la relation arbitre / joueurs, peu importe le joueur blessé et attendre que les autres joueurs continuent à travailler. Si l'arbitre dit d'attendre ( Réinitialiser ()
), tous les joueurs attendront 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();
}
en tant qu'opération atomique
OK, normalement, il n'est pas conseillé d'ajouter 2 réponses dans le même fil, mais je ne voulais pas modifier / supprimer ma réponse précédente, car cela pourrait aider d'une autre manière.
Maintenant, j'ai créé l'extrait de code ci-dessous, beaucoup plus complet et facile à comprendre, pour l'application de la console d'exécution.
Exécutez simplement les exemples sur deux consoles différentes et observez 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 un é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. Généralement, mais pas nécessairement, le "quelque chose" cela est arrivé 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.
Oui, c'est ça.
Vous pouvez vous faire une idée en utilisant ces deux-là.
Si vous devez indiquer que vous avez terminé votre travail et que d'autres (threads) peuvent attendre, vous devez utiliser ManualResetEvent.
Si vous devez disposer d'un accès exclusif mutuel à une ressource, vous devez utiliser AutoResetEvent.
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 true, quitte la boucle et affiche le "Signal obtenu par le thread".
Définir la méthode
La méthode AutoResetEvent Set a envoyé le signal au thread en attente pour qu'il poursuive son travail. Vous trouverez ci-dessous la syntaxe d'appel de la méthode Set.
autoResetEvent.Set();
ManualResetEvent conserve une variable booléenne en mémoire. Lorsque la variable booléenne est fausse, 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 vraie, 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. Elle renvoie true si elle reçoit un signal, sinon false.
La syntaxe d'appel de la méthode WaitOne est décrite ci-dessous.
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 renvoie false et passe à la ligne suivante de la méthode.
Ci-dessous, la syntaxe d'appel de 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 vont plus loin.
La syntaxe de la méthode Set () est indiquée ci-dessous.
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 (). La méthode de réinitialisation modifie la valeur booléenne en false.
La syntaxe d'appel de la méthode de réinitialisation est la suivante.
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.
Si vous voulez comprendre AutoResetEvent et ManualResetEvent, vous devez comprendre non pas le threading, mais les interruptions!
.NET souhaite créer la programmation de bas niveau la plus éloignée possible.
Une interruption est un élément 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 sur la fonction qui gère cet événement .
La première chose à faire lorsqu'une interruption se produit est de réinitialiser son état, car le matériel fonctionne de cette manière:
- une broche est connectée à un signal et le matériel l'écoute (le signal ne peut avoir que deux états).
- si le signal change, cela signifie que quelque chose s'est passé et que le matériel a mis une variable de mémoire à l'état qui s'est passé (et qu'il en reste ainsi même si le signal change à nouveau).
- le programme remarque que la variable change d'état et déplace l'exécution vers une fonction de traitement.
- La première chose à faire, pour pouvoir réécouter cette interruption, est de réinitialiser cette variable de mémoire à l'état non passé.
C'est la différence entre ManualResetEvent et AutoResetEvent.
Si un événement ManualResetEvent se produit et que je ne le réinitialise pas, il ne pourra plus être écouté la prochaine fois.