Przeczytałem dokumentację na ten temat i myślę, że rozumiem. An AutoResetEvent
resetuje się, gdy przechodzi przez kod event.WaitOne()
, ale ManualResetEvent
nie.
Czy to jest poprawne?
Przeczytałem dokumentację na ten temat i myślę, że rozumiem. An AutoResetEvent
resetuje się, gdy przechodzi przez kod event.WaitOne()
, ale ManualResetEvent
nie.
Czy to jest poprawne?
Odpowiedzi:
Tak. To jak różnica między bramką a drzwiami. Są ManualResetEvent
to drzwi, które należy zamknąć (zresetować) ręcznie. Jest AutoResetEvent
to punkt poboru opłat, umożliwiający przejazd jednego samochodu i automatyczne zamykanie się przed przejściem następnego.
Wystarczy wyobrazić sobie, że AutoResetEvent
sporządzi WaitOne()
i Reset()
jako jednej operacji atomowej.
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).
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.
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();
}
}
}
autoResetEvent.WaitOne()
jest podobne do
try
{
manualResetEvent.WaitOne();
}
finally
{
manualResetEvent.Reset();
}
jako operacja atomowa
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);
}
}
}
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);
}
}
}
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.
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.
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.
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:
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ć.