Jaka jest różnica między ManualResetEvent i AutoResetEvent w .NET?

Odpowiedzi:

919

Tak. To jak różnica między bramką a drzwiami. Są ManualResetEventto drzwi, które należy zamknąć (zresetować) ręcznie. Jest AutoResetEventto punkt poboru opłat, umożliwiający przejazd jednego samochodu i automatyczne zamykanie się przed przejściem następnego.

Dan Goldstein
źródło
166
To świetna analogia.
twk
Co gorsza, nie czekaj długo, aby ustawić ARE na WaitOne, w przeciwnym razie zostanie zresetowany. Miałem z tym wiele porzuconych wątków.
Oliver Friedrich,
24
Lub jak drzwi i bramka.
Constantin
9
Och, dlatego nazwano ich takimi, jakimi są.
Arlen Beiler
1
@ DanGoldstein dobrze, ponieważ to nie jest zamknięte i na wypadek, gdyby ktoś inny tego chciał: msdn.microsoft.com/en-us/library/...
Phil N DeBlanc
124

Wystarczy wyobrazić sobie, że AutoResetEventsporządzi WaitOne()i Reset()jako jednej operacji atomowej.

Michael Damatov
źródło
16
Tyle, że jeśli wykonałeś WaitOne i Reset jako pojedynczą operację atomową na zdarzeniu ManualResetEvent, nadal zrobiłby coś innego niż AutoResetEvent. ManualResetEvent zwalnia wszystkie oczekujące wątki jednocześnie, podczas gdy AutoResetEvent gwarantuje zwolnienie tylko jednego wątku oczekującego.
Martin Brown
55

Krótka odpowiedź brzmi: tak. Najważniejszą różnicą jest to, że AutoResetEvent pozwoli na kontynuację tylko jednego wątku oczekującego. Z drugiej strony ManualResetEvent będzie nadal pozwalał na kontynuowanie wątków, nawet kilku w tym samym czasie, dopóki nie powiesz, aby przestał (Zresetuj).

Martin Brown
źródło
36

Zaczerpnięte z książki Nutshell C # 3.0 autorstwa Josepha Albahari

Wątki w C # - bezpłatny e-book

ManualResetEvent to odmiana AutoResetEvent. Różni się tym, że nie resetuje się automatycznie po przepuszczeniu wątku w wywołaniu WaitOne, a więc działa jak brama: wywołanie Set otwiera bramę, umożliwiając dowolną liczbę wątków, przez które przechodzi WaitOne; wywołanie Reset zamyka bramę, potencjalnie powodując, że kolejka kelnerów gromadzi się aż do jej następnego otwarcia.

Można symulować tę funkcjonalność za pomocą logicznego pola „gateOpen” (zadeklarowanego zmiennym słowem kluczowym) w połączeniu z „spin-sleep” - wielokrotnie sprawdzając flagę, a następnie śpiąc przez krótki czas.

ManualResetEvents są czasami używane do sygnalizowania, że ​​dana operacja została zakończona lub że inicjalizacja wątku jest gotowa do wykonania pracy.


źródło
19

Stworzyłem proste przykłady, aby wyjaśnić zrozumienie ManualResetEvent vs AutoResetEvent.

AutoResetEvent: załóżmy, że masz 3 wątki pracowników. Jeśli którykolwiek z tych wątków wywoła WaitOne()wszystkie pozostałe 2 wątki zatrzymają wykonywanie i będą czekać na sygnał. Zakładam, że używają WaitOne(). To jest jak; jeśli nie pracuję, nikt nie działa. W pierwszym przykładzie możesz to zobaczyć

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

Kiedy zadzwonisz, Set()wszystkie wątki będą działać i będą czekać na sygnał. Po 1 sekundzie wysyłam drugi sygnał, a one wykonują polecenie i czekają ( WaitOne()). Pomyśl o tych facetach, którzy grają w drużyny piłkarskie, a jeśli jeden z graczy powie, że poczekam, aż menedżer do mnie zadzwoni, a inni poczekają, aż menedżer powie im, aby kontynuowali ( 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();
        }
    }
}

W tym przykładzie widać wyraźnie, że po pierwszym uderzeniu Set()puści wszystkie wątki, a po 1 sekundzie sygnalizuje wszystkim wątkom, że muszą czekać! Gdy tylko ustawisz je ponownie, niezależnie od tego, czy dzwonią w WaitOne()środku, będą one działać, ponieważ musisz ręcznie dzwonićReset() aby zatrzymać je wszystkie.

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

Chodzi raczej o relację sędziów / zawodników tam, niezależnie od tego, który z zawodników jest kontuzjowany i czeka na grę inni będą kontynuować pracę. Jeśli Sędzia mówi czekaj ( Reset()), wszyscy gracze będą czekać do następnego sygnału.

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
źródło
13

autoResetEvent.WaitOne()

jest podobne do

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

jako operacja atomowa

vezenkov
źródło
Jest to tylko poprawne koncepcyjnie, ale nie praktycznie. Między WaitOne a Resetem może wystąpić zmiana kontekstu; może to prowadzić do subtelnych błędów.
hofingerandi
2
Czy możesz teraz zagłosować? Nikt praktycznie nie zrobi tutaj drugiego bloku kodu, to kwestia zrozumienia różnicy.
vezenkov
11

OK, zwykle nie jest dobrą praktyką dodawanie 2 odpowiedzi w tym samym wątku, ale nie chciałem edytować / usuwać mojej poprzedniej odpowiedzi, ponieważ może to pomóc w inny sposób.

Teraz stworzyłem, o wiele bardziej wszechstronny i łatwy do zrozumienia, fragment aplikacji konsoli do uruchomienia poniżej.

Po prostu uruchom przykłady na dwóch różnych konsolach i obserwuj zachowanie. Dowiesz się o wiele wyraźniej, co dzieje się za kulisami.

Zdarzenie resetowania ręcznego

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

}

Wyjście zdarzenia resetowania ręcznego

Zdarzenie automatycznego resetu

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

}

Wyjście zdarzenia automatycznego resetowania

Teoman Shipahi
źródło
to był najlepszy sposób, aby to wszystko zrozumieć, skopiowałem kod i uruchomiłem to wszystko, zmieniając kilka rzeczy, dobrze to zrozumiałem
JohnChris
8

AutoResetEvent utrzymuje w pamięci zmienną boolowską. Jeśli zmienna boolowska ma wartość false, wówczas blokuje wątek, a jeśli zmienna boolowska ma wartość true, odblokowuje wątek.

Kiedy tworzymy instancję obiektu AutoResetEvent, przekazujemy domyślną wartość wartości boolowskiej do konstruktora. Poniżej znajduje się składnia wystąpienia obiektu AutoResetEvent.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Metoda WaitOne

Ta metoda blokuje bieżący wątek i czeka na sygnał innego wątku. Metoda WaitOne ustawia bieżący wątek w stan wątku uśpienia. Metoda WaitOne zwraca true, jeśli odbiera sygnał, w przeciwnym razie zwraca false.

autoResetEvent.WaitOne();

Drugie przeciążenie metody WaitOne odczekaj określoną liczbę sekund. Jeśli nic nie otrzyma, wątek kontynuuje pracę.

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

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

Wywołaliśmy metodę WaitOne, przekazując 2 sekundy jako argumenty. W pętli while czeka na sygnał przez 2 sekundy, a następnie kontynuuje pracę. Gdy wątek otrzyma sygnał, WaitOne zwraca wartość true, wychodzi z pętli i drukuje komunikat „Wątek dostał sygnał”.

Ustaw metodę

Metoda AutoResetEvent Set wysłała sygnał do oczekującego wątku, aby kontynuować pracę. Poniżej znajduje się składnia wywołania metody Set.

autoResetEvent.Set();

ManualResetEvent utrzymuje w pamięci zmienną boolowską. Gdy zmienna boolowska ma wartość false, wówczas blokuje wszystkie wątki, a gdy zmienna boolowska ma wartość true, odblokowuje wszystkie wątki.

Kiedy tworzymy instancję ManualResetEvent, inicjalizujemy ją z domyślną wartością logiczną.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

W powyższym kodzie inicjujemy ManualResetEvent wartością false, co oznacza, że ​​wszystkie wątki wywołujące metodę WaitOne będą blokowane, dopóki niektóre wątki nie wywołają metody Set ().

Jeśli zainicjujemy ManualResetEvent z wartością true, wszystkie wątki wywołujące metodę WaitOne nie będą blokować i będą mogły kontynuować.

Metoda WaitOne

Ta metoda blokuje bieżący wątek i czeka na sygnał innego wątku. Zwraca true, jeśli odbiera sygnał, w przeciwnym razie false.

Poniżej znajduje się składnia wywołania metody WaitOne.

manualResetEvent.WaitOne();

W drugim przeciążeniu metody WaitOne możemy określić przedział czasu do momentu, aż bieżący wątek będzie czekać na sygnał. Jeśli w czasie wewnętrznym, nie odbiera sygnału, zwraca false i przechodzi do następnej linii metody.

Poniżej znajduje się składnia wywołania metody WaitOne z interwałem czasowym.

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

Określiliśmy 5 sekund na metodę WaitOne. Jeśli obiekt manualResetEvent nie odbiera sygnału przez 5 sekund, ustawia zmienną isSignalled na false.

Ustaw metodę

Ta metoda służy do wysyłania sygnału do wszystkich oczekujących wątków. Metoda Set () ustaw zmienną boolean obiektu ManualResetEvent na wartość true. Wszystkie oczekujące wątki są odblokowane i idą dalej.

Poniżej znajduje się składnia wywołania metody Set ().

manualResetEvent.Set();

Metoda resetowania

Po wywołaniu metody Set () na obiekcie ManualResetEvent jej wartość logiczna pozostaje prawdziwa. Aby zresetować wartość, możemy użyć metody Reset (). Metoda resetowania zmienia wartość logiczną na false.

Poniżej znajduje się składnia wywołania metody Reset.

manualResetEvent.Reset();

Musimy natychmiast wywołać metodę Reset po wywołaniu metody Set, jeśli chcemy wielokrotnie wysyłać sygnał do wątków.

Masoud Siahkali
źródło
7

Tak. To jest absolutnie poprawne.

Możesz zobaczyć ManualResetEvent jako sposób na wskazanie stanu. Coś jest włączone (Ustaw) lub wyłączone (Zresetuj). Wystąpienie o pewnym czasie trwania. Każdy wątek oczekujący na taki stan może być kontynuowany.

AutoResetEvent jest bardziej porównywalny do sygnału. Jeden strzał wskazujący, że coś się stało. Wystąpienie bez żadnego czasu trwania. Zazwyczaj, choć niekoniecznie, „coś”, co się wydarzyło, jest małe i musi być obsługiwane przez pojedynczy wątek - stąd automatyczne resetowanie po zużyciu zdarzenia przez pojedynczy wątek.

Boaz
źródło
7

Tak to prawda.

Możesz uzyskać pomysł, korzystając z tych dwóch.

Jeśli chcesz powiedzieć, że skończyłeś trochę pracy i inne (wątki), które na to czekają, mogą teraz kontynuować, powinieneś użyć ManualResetEvent.

Jeśli potrzebujesz wzajemnego wyłącznego dostępu do dowolnego zasobu, powinieneś użyć AutoResetEvent.

Swapnil Patil
źródło
1

Jeśli chcesz zrozumieć AutoResetEvent i ManualResetEvent, musisz zrozumieć, że nie ma wątków, ale przeszkadza!

.NET chce wyczarować programowanie niskiego poziomu jak najdalej.

Przerwania są czymś używanym w programowaniu niskiego poziomu, co jest równoznaczne z sygnałem, który z niskiego stał się wysoki (lub odwrotnie). Kiedy tak się dzieje, program przerywa normalne wykonywanie i przesuwa wskaźnik wykonania do funkcji, która obsługuje to zdarzenie .

Pierwszą rzeczą do zrobienia po wystąpieniu przerwania jest zresetowanie jego stanu, ponieważ sprzęt działa w ten sposób:

  1. pin jest podłączony do sygnału i sprzęt oczekuje na jego zmianę (sygnał może mieć tylko dwa stany).
  2. zmiana sygnału oznacza, że ​​coś się wydarzyło, a sprzęt umieścił zmienną pamięci do tego stanu (i tak pozostanie, nawet jeśli sygnał zmieni się ponownie).
  3. program zauważa, że ​​zmienne zmieniają stany i przenoszą wykonanie do funkcji obsługi.
  4. tutaj pierwszą rzeczą do zrobienia, aby móc ponownie usłyszeć to przerwanie, jest zresetowanie tej zmiennej pamięci do stanu nieistniejącego.

Jest to różnica między ManualResetEvent i AutoResetEvent.
Jeśli zdarzy się ManualResetEvent i nie zresetuję go, następnym razem nie będzie w stanie go słuchać.

zasada
źródło