Was ist der Unterschied zwischen ManualResetEvent und AutoResetEvent in .NET?

Antworten:

920

Ja. Es ist wie der Unterschied zwischen einer Mautstelle und einer Tür. Das ManualResetEventist die Tür, die manuell geschlossen (zurückgesetzt) ​​werden muss. Das AutoResetEventist eine Mautstelle, an der ein Auto vorbeifahren und automatisch schließen kann, bevor das nächste durchkommt.

Dan Goldstein
quelle
166
Das ist eine großartige Analogie.
Twk
Schlimmer noch, warten Sie nicht zu lange, bis Sie ARE auf WaitOne eingestellt haben. Andernfalls wird es in der Zwischenzeit zurückgesetzt. Hatte damit viele verlassene Fäden.
Oliver Friedrich
24
Oder wie eine Tür und ein Drehkreuz.
Constantin
9
Oh, deshalb werden sie so genannt, wie sie sind.
Arlen Beiler
1
@ DanGoldstein gut, da dies nicht geschlossen ist und falls jemand anderes es will: msdn.microsoft.com/en-us/library/…
Phil N DeBlanc
124

Stellen Sie sich vor, das wird AutoResetEventausgeführt WaitOne()und Reset()als einzelne atomare Operation.

Michael Damatov
quelle
16
Wenn Sie WaitOne und Reset als einzelne atomare Operation für ein ManualResetEvent-Ereignis ausführen, wird jedoch immer noch etwas anderes als bei einem AutoResetEvent ausgeführt. Das ManualResetEvent gibt alle wartenden Threads gleichzeitig frei, wobei das AutoResetEvent garantiert, dass nur ein wartender Thread freigegeben wird.
Martin Brown
55

Die kurze Antwort lautet ja. Der wichtigste Unterschied besteht darin, dass mit einem AutoResetEvent nur ein einziger wartender Thread fortgesetzt werden kann. Ein ManualResetEvent hingegen ermöglicht es Threads, mehrere gleichzeitig, fortzufahren, bis Sie anweisen, dass sie gestoppt werden sollen (Zurücksetzen).

Martin Brown
quelle
36

Entnommen aus dem C # 3.0 Nutshell-Buch von Joseph Albahari

Threading in C # - Kostenloses E-Book

Ein ManualResetEvent ist eine Variation von AutoResetEvent. Es unterscheidet sich darin, dass es nicht automatisch zurückgesetzt wird, nachdem ein Thread bei einem WaitOne-Aufruf durchgelassen wurde, und daher wie ein Gate funktioniert: Das Aufrufen von Set öffnet das Gate und lässt eine beliebige Anzahl von Threads zu, die WaitOne am Gate durchlässt. Durch das Aufrufen von Reset wird das Gate geschlossen, wodurch sich möglicherweise eine Warteschlange von Kellnern ansammelt, bis es das nächste Mal geöffnet wird.

Man könnte diese Funktionalität mit einem booleschen "gateOpen" -Feld (deklariert mit dem flüchtigen Schlüsselwort) in Kombination mit "Spin-Sleeping" simulieren - wiederholt das Flag überprüfen und dann für einen kurzen Zeitraum schlafen.

ManualResetEvents werden manchmal verwendet, um zu signalisieren, dass ein bestimmter Vorgang abgeschlossen ist oder dass die Initialisierung eines Threads abgeschlossen ist und zur Ausführung von Arbeiten bereit ist.


quelle
19

Ich habe einfache Beispiele erstellt, um das Verständnis von ManualResetEventvs zu verdeutlichen AutoResetEvent.

AutoResetEvent: Nehmen wir an, Sie haben 3 Worker-Thread. Wenn einer dieser Threads WaitOne()alle anderen 2 Threads aufruft, wird die Ausführung gestoppt und auf das Signal gewartet. Ich gehe davon aus, dass sie verwenden WaitOne(). Es ist wie; Wenn ich nicht arbeite, arbeitet niemand. Im ersten Beispiel können Sie das sehen

autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();

Wenn Sie aufrufen Set(), funktionieren alle Threads und warten auf das Signal. Nach 1 Sekunde sende ich ein zweites Signal und sie werden ausgeführt und warten ( WaitOne()). Denken Sie daran, dass diese Jungs Fußballspieler sind und wenn ein Spieler sagt, ich werde warten, bis der Manager mich anruft, und andere warten, bis der Manager ihnen sagt, dass sie fortfahren sollen ( 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();
        }
    }
}

In diesem Beispiel können Sie deutlich sehen, dass beim ersten Treffer Set()alle Threads losgelassen werden und nach 1 Sekunde alle Threads zum Warten aufgefordert werden! Sobald Sie sie erneut einstellen, unabhängig davon, ob sie im Haus anrufen WaitOne(), werden sie weiter ausgeführt, da Sie manuell anrufen müssen Reset(), um sie alle zu stoppen.

manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();

Es geht mehr um die Beziehung zwischen Schiedsrichter und Spieler, unabhängig davon, ob einer der Spieler verletzt ist oder ob das Warten auf das Spielen anderer weiterhin funktioniert. Wenn der Schiedsrichter wait ( Reset()) sagt, warten alle Spieler bis zum nächsten 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();
        }
    }
}
Teoman Shipahi
quelle
13

autoResetEvent.WaitOne()

ist ähnlich wie

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

als atomare Operation

vezenkov
quelle
Dies ist nur konzeptionell korrekt, aber nicht praktisch. Zwischen WaitOne und Reset kann ein Kontextwechsel auftreten. Dies kann zu subtilen Fehlern führen.
Hofingerandi
2
Könnten Sie es jetzt abstimmen? Hier wird praktisch niemand den zweiten Codeblock ausführen, es geht darum, den Unterschied zu verstehen.
Vezenkov
11

OK, normalerweise ist es keine gute Praxis, 2 Antworten im selben Thread hinzuzufügen, aber ich wollte meine vorherige Antwort nicht bearbeiten / löschen, da dies auf andere Weise hilfreich sein kann.

Jetzt habe ich unten ein viel umfassenderes und leicht verständliches Run-to-Learn-Konsolen-App-Snippet erstellt.

Führen Sie die Beispiele einfach auf zwei verschiedenen Konsolen aus und beobachten Sie das Verhalten. Sie werden dort eine viel klarere Vorstellung davon bekommen, was hinter den Kulissen passiert.

Manuelles Zurücksetzen

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

}

Manuelle Ereignisausgabe zurücksetzen

Auto Reset-Ereignis

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

}

Ereignisausgabe automatisch zurücksetzen

Teoman Shipahi
quelle
Dies war der beste Weg, um alles zu verstehen, den Code zu kopieren und alles auszuführen, während ein paar Dinge
geändert wurden
8

AutoResetEvent verwaltet eine boolesche Variable im Speicher. Wenn die boolesche Variable false ist, wird der Thread blockiert, und wenn die boolesche Variable true ist, wird der Thread entsperrt.

Wenn wir ein AutoResetEvent-Objekt instanziieren, übergeben wir den Standardwert des booleschen Werts im Konstruktor. Unten finden Sie die Syntax zum Instanziieren eines AutoResetEvent-Objekts.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

WaitOne-Methode

Diese Methode blockiert den aktuellen Thread und wartet auf das Signal eines anderen Threads. Die WaitOne-Methode versetzt den aktuellen Thread in einen Ruhezustand. Die WaitOne-Methode gibt true zurück, wenn sie das Signal empfängt. Andernfalls wird false zurückgegeben.

autoResetEvent.WaitOne();

Die zweite Überladung der WaitOne-Methode wartet auf die angegebene Anzahl von Sekunden. Wenn es keinen Signal-Thread erhält, setzt es seine Arbeit fort.

static void ThreadMethod()
{
    while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))
    {
        Console.WriteLine("Continue");
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }

    Console.WriteLine("Thread got signal");
}

Wir haben die WaitOne-Methode aufgerufen, indem wir die 2 Sekunden als Argumente übergeben haben. In der while-Schleife wartet es 2 Sekunden auf das Signal und setzt dann seine Arbeit fort. Wenn der Thread das Signal erhalten hat, gibt WaitOne true zurück und verlässt die Schleife und gibt das "Thread got signal" aus.

Methode einstellen

Die AutoResetEvent Set-Methode hat das Signal an den wartenden Thread gesendet, um seine Arbeit fortzusetzen. Unten finden Sie die Syntax zum Aufrufen der Set-Methode.

autoResetEvent.Set();

ManualResetEvent verwaltet eine boolesche Variable im Speicher. Wenn die boolesche Variable false ist, werden alle Threads blockiert, und wenn die boolesche Variable true ist, werden alle Threads entsperrt.

Wenn wir ein ManualResetEvent instanziieren, initialisieren wir es mit dem Standard-Booleschen Wert.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

Im obigen Code initialisieren wir das ManualResetEvent mit einem falschen Wert. Dies bedeutet, dass alle Threads, die die WaitOne-Methode aufrufen, blockiert werden, bis ein Thread die Set () -Methode aufruft.

Wenn wir ManualResetEvent mit dem Wert true initialisieren, werden alle Threads, die die WaitOne-Methode aufrufen, nicht blockiert und können fortfahren.

WaitOne-Methode

Diese Methode blockiert den aktuellen Thread und wartet auf das Signal eines anderen Threads. Es gibt true zurück, wenn es ein Signal empfängt, andernfalls false.

Unten finden Sie die Syntax zum Aufrufen der WaitOne-Methode.

manualResetEvent.WaitOne();

In der zweiten Überladung der WaitOne-Methode können wir das Zeitintervall angeben, bis der aktuelle Thread auf das Signal wartet. Wenn es innerhalb der internen Zeit kein Signal empfängt, gibt es false zurück und geht in die nächste Methodenzeile.

Unten finden Sie die Syntax zum Aufrufen der WaitOne-Methode mit Zeitintervall.

bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));

Wir haben 5 Sekunden in der WaitOne-Methode angegeben. Wenn das manualResetEvent-Objekt zwischen 5 Sekunden kein Signal empfängt, setzt es die Variable isSignalled auf false.

Methode einstellen

Diese Methode wird zum Senden des Signals an alle wartenden Threads verwendet. Set () -Methode Setzen Sie die boolesche Variable des ManualResetEvent-Objekts auf true. Alle wartenden Threads werden entsperrt und fahren fort.

Unten finden Sie die Syntax zum Aufrufen der Set () -Methode.

manualResetEvent.Set();

Methode zurücksetzen

Sobald wir die Set () -Methode für das ManualResetEvent-Objekt aufrufen, bleibt ihr Boolescher Wert true. Um den Wert zurückzusetzen, können wir die Reset () -Methode verwenden. Die Methode zum Zurücksetzen ändert den booleschen Wert in false.

Unten finden Sie die Syntax zum Aufrufen der Reset-Methode.

manualResetEvent.Reset();

Wir müssen die Reset-Methode sofort nach dem Aufruf der Set-Methode aufrufen, wenn wir mehrmals ein Signal an Threads senden möchten.

Masoud Siahkali
quelle
7

Ja. Das ist absolut richtig.

Sie können ManualResetEvent als eine Möglichkeit sehen, den Status anzuzeigen. Etwas ist ein (Set) oder aus (Reset). Ein Ereignis mit einer gewissen Dauer. Jeder Thread, der auf diesen Status wartet, kann fortfahren.

Ein AutoResetEvent ist eher mit einem Signal vergleichbar. Ein One-Shot-Hinweis darauf, dass etwas passiert ist. Ein Ereignis ohne Dauer. Normalerweise, aber nicht unbedingt, ist das "Etwas", das passiert ist, klein und muss von einem einzelnen Thread verarbeitet werden - daher das automatische Zurücksetzen, nachdem ein einzelner Thread das Ereignis verbraucht hat.

Boas
quelle
7

Ja, das ist richtig.

Durch die Verwendung dieser beiden können Sie sich ein Bild machen.

Wenn Sie feststellen müssen, dass Sie mit einigen Arbeiten fertig sind und andere (Threads) darauf warten können, sollten Sie ManualResetEvent verwenden.

Wenn Sie gegenseitigen exklusiven Zugriff auf eine Ressource benötigen, sollten Sie AutoResetEvent verwenden.

Swapnil Patil
quelle
1

Wenn Sie AutoResetEvent und ManualResetEvent verstehen möchten, müssen Sie nicht Threading, sondern Interrupts verstehen!

.NET möchte Low-Level-Programmierung so weit wie möglich heraufbeschwören.

Ein Interrupt wird in der Low-Level-Programmierung verwendet und entspricht einem Signal, das von Low zu High wurde (oder umgekehrt). In diesem Fall unterbricht das Programm seine normale Ausführung und bewegt den Ausführungszeiger auf die Funktion, die dieses Ereignis behandelt .

Das erste, was zu tun ist, wenn ein Interrupt passiert ist, ist das Zurücksetzen seines Status, da die Hardware folgendermaßen funktioniert:

  1. Ein Pin ist mit einem Signal verbunden und die Hardware wartet darauf, dass es sich ändert (das Signal kann nur zwei Zustände haben).
  2. Wenn sich das Signal ändert, bedeutet dies, dass etwas passiert ist und die Hardware eine Speichervariable in den Status versetzt hat (und dies bleibt auch dann so, wenn sich das Signal erneut ändert).
  3. Das Programm bemerkt, dass die Variablen den Status ändern und die Ausführung in eine Handhabungsfunktion verschieben.
  4. hier , um das erste , was zu tun, wieder diese Unterbrechung hören zu können, ist zurückgesetzt diese Speichervariable in dem Zustand nicht-passiert ist .

Dies ist der Unterschied zwischen ManualResetEvent und AutoResetEvent.
Wenn ein ManualResetEvent auftritt und ich es nicht zurücksetze, kann ich es beim nächsten Mal nicht anhören.

princio
quelle