Quelle est la différence entre ManualResetEvent et AutoResetEvent dans .NET?


Réponses:


920

Oui. C'est comme la différence entre un péage et une porte. C'est ManualResetEventla porte qui doit être fermée (réinitialisée) manuellement. Il AutoResetEvents'agit d'un péage, permettant à une voiture de passer et se fermant automatiquement avant que la suivante puisse passer.


166
C'est une grande analogie.
Twk

Pire encore, n'attendez pas trop longtemps pour régler l'ARE sur WaitOne, sinon il sera réinitialisé en attendant. Il y avait beaucoup de fils abandonnés avec ça.
Oliver Friedrich

24
Ou comme une porte et un tourniquet.
Constantin

9
Oh, c'est pourquoi ils sont nommés ce qu'ils sont.
Arlen Beiler

1
@DanGoldstein bien, car ce n'est pas fermé et au cas où quelqu'un d'autre le voudrait: msdn.microsoft.com/en-us/library/…
Phil N DeBlanc

124

Imaginez simplement que le AutoResetEventexécute WaitOne()et Reset()comme une seule opération atomique.


16
Sauf que si vous exécutiez WaitOne et Reset comme une seule opération atomique sur un événement ManualResetEvent, il ferait toujours quelque chose de différent d'un AutoResetEvent. ManualResetEvent libère tous les threads en attente en même temps, alors que AutoResetEvent garantit de ne libérer qu'un seul thread en attente.
Martin Brown

55

La reponse courte est oui. La différence la plus importante est qu'un AutoResetEvent ne permettra à un seul thread en attente de continuer. Un événement ManualResetEvent, d'autre part, continuera à permettre aux threads, plusieurs en même temps, de continuer jusqu'à ce que vous lui disiez de s'arrêter (réinitialisez-le).


36

Tiré du livre C # 3.0 Nutshell, par Joseph Albahari

Filetage en C # - E-Book gratuit

Un ManualResetEvent est une variante d'AutoResetEvent. Il diffère par le fait qu'il ne se réinitialise pas automatiquement une fois qu'un thread est passé à travers un appel WaitOne, et fonctionne donc comme une porte: l'appel de Set ouvre la porte, autorisant n'importe quel nombre de threads que WaitOne à la porte traverse; L'appel à Reset ferme la porte, provoquant potentiellement 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" - 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 est prête à effectuer le travail.


19

J'ai créé des exemples simples pour clarifier la compréhension de ManualResetEventvs AutoResetEvent.

AutoResetEvent: supposons que vous ayez 3 threads de travail. Si l'un de ces threads appelle WaitOne()les 2 autres threads, l'exécution s'arrête et attend 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 d'équipe de football et si un joueur dit que j'attendrai que le manager m'appelle, et que les autres attendent que le manager 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 appuyez pour la première fois, Set()tous les threads disparaissent, puis après 1 seconde, il signale à tous les threads d'attendre! Dès que vous les redéfinissez, quel que soit leur appel WaitOne(), ils continueront 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 là-bas, indépendamment du fait qu'un joueur soit blessé et attendre que les autres continuent de fonctionner. Si l'arbitre dit wait ( Reset()), tous les joueurs attendront le signal suivant.

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();
        }
    }
}

13

autoResetEvent.WaitOne()

est similaire à

try
{
   manualResetEvent.WaitOne();
}
finally
{
   manualResetEvent.Reset();
}

comme une opération atomique


Ce n'est que conceptuellement correct, mais pas en pratique. Entre WaitOne et Reset, un changement de contexte peut se produire; cela peut conduire à des bugs subtils.
hofingerandi

2
Pourriez-vous voter pour maintenant? Personne ne fera pratiquement le deuxième bloc de code ici, c'est une question de comprendre la différence.
vezenkov

11

OK, normalement, ce n'est pas une bonne pratique 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 peut aider d'une autre manière.

Maintenant, j'ai créé un extrait d'application de console run-to-learn, beaucoup plus complet et facile à comprendre ci-dessous.

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.

Évé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);
        }
    }

}

Sortie d'événement de réinitialisation manuelle

Évé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);
        }
    }

}

Sortie d'événement de réinitialisation automatique


c'était la meilleure façon de tout comprendre, de copier le code et de le faire tourner tout en changeant quelques choses, le comprenait bien maintenant
JohnChris

8

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 transmettons la valeur par défaut de la valeur booléenne dans le constructeur. Vous trouverez ci-dessous la syntaxe d'instanciation d'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 actuel dans un état de thread de veille. La méthode WaitOne renvoie true si elle reçoit le signal, sinon renvoie false.

autoResetEvent.WaitOne();

La deuxième surcharge de la méthode WaitOne attend le nombre de secondes spécifié. S'il n'obtient aucun fil de signal 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 obtenu le signal WaitOne retourne vrai et quitte la boucle et affiche le "Thread got signal".

Définir la méthode

La méthode AutoResetEvent Set a envoyé le signal au thread en attente pour poursuivre son travail. Voici la syntaxe de l'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 la valeur booléenne par défaut.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

Dans le code ci-dessus, nous initialisons le ManualResetEvent avec une valeur fausse, ce qui signifie que tous les threads qui appellent la méthode WaitOne seront bloqués jusqu'à ce que certains threads appellent la méthode Set ().

Si nous initialisons ManualResetEvent avec la valeur true, tous les threads qui appellent la méthode WaitOne ne seront pas bloqués et 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 renvoie faux.

Voici la syntaxe de l'appel de la méthode WaitOne.

manualResetEvent.WaitOne();

Dans la deuxième surcharge de la méthode WaitOne, nous pouvons spécifier l'intervalle de temps jusqu'à ce que le thread actuel attende le signal. Si dans le temps interne, il ne reçoit pas de signal, il retourne faux et passe à la ligne de méthode suivante.

Voici la syntaxe de l'appel de la méthode WaitOne avec 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 définit la variable isSignalled sur false.

Définir la méthode

Cette méthode est utilisée pour envoyer le signal à tous les threads en attente. La 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.

Voici la syntaxe de l'appel de la méthode Set ().

manualResetEvent.Set();

Méthode de réinitialisation

Une fois que nous appelons la méthode Set () sur l'objet ManualResetEvent, son booléen reste vrai. Pour réinitialiser la valeur, nous pouvons utiliser la méthode Reset (). La méthode de réinitialisation remplace la valeur booléenne par false.

Voici la syntaxe de l'appel de 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 plusieurs fois le signal aux threads.


7

Oui. C'est absolument correct.

Vous pouvez voir ManualResetEvent comme un moyen d'indiquer l'état. Quelque chose est activé (défini) ou désactivé (réinitialisé). Un événement d'une certaine durée. Tout thread en attente de cet état peut se poursuivre.

Un AutoResetEvent est plus comparable à un signal. Une indication unique que quelque chose s'est produit. Un événement sans durée. En règle générale, mais pas nécessairement, le «quelque chose» qui s'est produit est petit et doit être géré par un seul thread - d'où la réinitialisation automatique après qu'un seul thread a consommé l'événement.


7

Oui c'est vrai.

Vous pouvez vous faire une idée en utilisant ces deux.

Si vous devez dire que vous avez terminé certains travaux et que d'autres (threads) en attente peuvent maintenant continuer, vous devez utiliser ManualResetEvent.

Si vous devez avoir un accès exclusif mutuel à n'importe quelle ressource, vous devez utiliser AutoResetEvent.


1

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 de bas est devenu haut (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 en cas d'interruption est de réinitialiser son état, car le matériel fonctionne de cette façon:

  1. une broche est connectée à un signal et le matériel attend qu'il change (le signal ne peut avoir que deux états).
  2. si le signal change signifie que quelque chose s'est passé et que le matériel a mis une variable de mémoire à l'état qui s'est produit (et cela reste comme ça même si le signal change à nouveau).
  3. le programme remarque que la variable change d'état et déplace l'exécution vers une fonction de gestion.
  4. ici la première chose à faire, pour pouvoir réécouter cette interruption, est de remettre cette variable mémoire à l'état non arrivé.

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 produit, je ne pourrai pas l'écouter.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.