Qual è la differenza tra ManualResetEvent e AutoResetEvent in .NET?


Risposte:


920

Sì. È come la differenza tra un casello e una porta. La ManualResetEventè la porta, che deve essere chiuso (reset) manualmente. Si AutoResetEventtratta di un casello che consente il passaggio di un'auto e la chiusura automatica prima che la successiva possa passare.


166
Questa è una grande analogia.
twk,

Ancora peggio, non aspettare troppo a lungo dall'impostazione di ARE su WaitOne, altrimenti verrà ripristinato nel frattempo. Aveva molti fili abbandonati con quello.
Oliver Friedrich,

24
O come una porta e un tornello.
Constantin,

9
Oh, ecco perché sono chiamati come sono.
Arlen Beiler,

1
@DanGoldstein bene, dal momento che questo non è chiuso e nel caso qualcun altro lo voglia: msdn.microsoft.com/en-us/library/…
Phil N DeBlanc,

124

Immagina che AutoResetEventesegua WaitOne()e Reset()come una singola operazione atomica.


16
Ad eccezione del fatto che se si esegue WaitOne e Reset come singola operazione atomica su un evento ManualResetEvent, ciò farebbe comunque qualcosa di diverso da un AutoResetEvent. ManualResetEvent rilascia tutti i thread in attesa contemporaneamente, mentre AutoResetEvent garantisce il rilascio di un solo thread in attesa.
Martin Brown,

55

La risposta breve è sì. La differenza più importante è che un AutoResetEvent consentirà a un solo thread in attesa di continuare. D'altra parte, un ManualResetEvent continuerà a consentire ai thread, diversi contemporaneamente, di continuare fino a quando non gli si dice di fermarlo (ripristinarlo).


36

Tratto dal libro Nutshell C # 3.0, di Joseph Albahari

Threading in C # - E-Book gratuito

Un ManualResetEvent è una variazione di AutoResetEvent. Si differenzia per il fatto che non si reimposta automaticamente dopo che un thread viene lasciato passare su una chiamata WaitOne, e quindi funziona come un gate: chiamando Set apre il gate, consentendo un numero qualsiasi di thread che WaitOne al gate attraversa; la chiamata di Reset chiude il gate, causando, potenzialmente, l'accumulo di una coda di camerieri fino alla successiva apertura.

Si potrebbe simulare questa funzionalità con un campo booleano "gateOpen" (dichiarato con la parola chiave volatile) in combinazione con "spin-sleeping" - controllando ripetutamente la bandiera e quindi dormendo per un breve periodo di tempo.

ManualResetEvents viene talvolta utilizzato per segnalare che una particolare operazione è stata completata o che l'inizializzazione è stata completata da un thread ed è pronta per eseguire il lavoro.


19

Ho creato semplici esempi per chiarire la comprensione della ManualResetEventvs AutoResetEvent.

AutoResetEvent: supponiamo che tu abbia 3 thread di lavoro. Se uno di questi thread chiamerà WaitOne()tutti gli altri 2 thread interromperà l'esecuzione e attenderà il segnale. Suppongo che stiano usando WaitOne(). È come; se non lavoro, nessuno funziona. Nel primo esempio puoi vederlo

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

Quando chiami Set()tutti i thread funzioneranno e aspetteranno il segnale. Dopo 1 secondo sto inviando un secondo segnale ed eseguono e attendono ( WaitOne()). Pensa a questi ragazzi che sono giocatori della squadra di calcio e se un giocatore dice che aspetterò fino a quando il manager mi chiama, e altri aspetteranno fino a quando il manager dice loro di continuare ( 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 questo esempio puoi vedere chiaramente che quando premi per la prima Set()volta lascerà andare tutti i thread, quindi dopo 1 secondo segnala a tutti i thread di aspettare! Non appena le imposti nuovamente, indipendentemente dal fatto che stiano chiamando WaitOne(), continueranno a funzionare perché devi chiamarle manualmente Reset()per interromperle tutte.

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

Si tratta di più sulla relazione Arbitro / Giocatori lì indipendentemente dal fatto che qualcuno del giocatore sia infortunato e aspettare che gli altri continuino a funzionare. Se l'arbitro dice wait ( Reset()), tutti i giocatori aspetteranno fino al segnale successivo.

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

è simile a

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

come operazione atomica


Questo è concettualmente corretto, ma non praticamente. Tra WaitOne e Reset potrebbe verificarsi un cambio di contesto; questo può portare a bug sottili.
hofingerandi,

2
Potresti votare adesso? Nessuno praticamente farà il secondo blocco di codice qui, è una questione di capire la differenza.
Vezenkov,

11

OK, normalmente non è una buona pratica aggiungere 2 risposte nello stesso thread, ma non volevo modificare / eliminare la mia risposta precedente, poiché può essere d'aiuto in un altro modo.

Ora ho creato di seguito uno snippet di app console molto più completo e di facile comprensione.

Basta eseguire gli esempi su due diverse console e osservare il comportamento. Avrai un'idea molto più chiara lì cosa sta succedendo dietro le quinte.

Evento di ripristino manuale

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

}

Uscita evento di ripristino manuale

Evento di ripristino automatico

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

}

Uscita evento reset automatico


questo era il modo migliore per capire tutto, copiare il codice ed
eseguirlo

8

AutoResetEvent mantiene una variabile booleana in memoria. Se la variabile booleana è falsa, blocca il thread e se la variabile booleana è true, sblocca il thread.

Quando istanziamo un oggetto AutoResetEvent, passiamo il valore predefinito del valore booleano nel costruttore. Di seguito è la sintassi di creare un'istanza di un oggetto AutoResetEvent.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Metodo WaitOne

Questo metodo blocca il thread corrente e attende il segnale da un altro thread. Il metodo WaitOne mette il thread corrente in uno stato di thread Sleep. Il metodo WaitOne restituisce true se riceve il segnale, altrimenti restituisce false.

autoResetEvent.WaitOne();

Secondo sovraccarico del metodo WaitOne attendere il numero specificato di secondi. In caso contrario, il thread del segnale continua a funzionare.

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

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

Abbiamo chiamato il metodo WaitOne passando i 2 secondi come argomenti. Nel ciclo while, attende il segnale per 2 secondi, quindi continua a funzionare. Quando il thread ha ottenuto il segnale WaitOne ritorna true ed esce dal loop e stampa il "Thread ottenuto segnale".

Imposta metodo

Il metodo Set AutoResetEvent ha inviato il segnale al thread in attesa per continuare il suo lavoro. Di seguito è riportata la sintassi della chiamata del metodo Set.

autoResetEvent.Set();

ManualResetEvent mantiene una variabile booleana in memoria. Quando la variabile booleana è falsa, blocca tutti i thread e quando la variabile booleana è vera, sblocca tutti i thread.

Quando istanziamo un ManualResetEvent, lo inizializziamo con il valore booleano predefinito.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

Nel codice sopra, inizializziamo ManualResetEvent con valore falso, ciò significa che tutti i thread che chiamano il metodo WaitOne verranno bloccati fino a quando alcuni thread chiamano il metodo Set ().

Se inizializziamo ManualResetEvent con il valore vero, tutti i thread che chiamano il metodo WaitOne non si bloccheranno e non procederanno liberamente per procedere ulteriormente.

Metodo WaitOne

Questo metodo blocca il thread corrente e attende il segnale da un altro thread. Restituisce vero se riceve un segnale altrimenti restituisce falso.

Di seguito è riportata la sintassi della chiamata del metodo WaitOne.

manualResetEvent.WaitOne();

Nel secondo sovraccarico del metodo WaitOne, possiamo specificare l'intervallo di tempo fino a quando il thread corrente attende il segnale. Se entro il tempo interno, non riceve un segnale, restituisce false e passa alla riga successiva del metodo.

Di seguito è riportata la sintassi della chiamata del metodo WaitOne con intervallo di tempo.

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

Abbiamo specificato 5 secondi nel metodo WaitOne. Se l'oggetto manualResetEvent non riceve un segnale tra 5 secondi, imposta la variabile isSignalled su false.

Imposta metodo

Questo metodo viene utilizzato per inviare il segnale a tutti i thread in attesa. Il metodo Set () imposta la variabile booleana dell'oggetto ManualResetEvent su true. Tutti i thread in attesa vengono sbloccati e procedono ulteriormente.

Di seguito è riportata la sintassi della chiamata del metodo Set ().

manualResetEvent.Set();

Metodo di ripristino

Una volta chiamato il metodo Set () sull'oggetto ManualResetEvent, il suo valore booleano rimane vero. Per resettare il valore possiamo usare il metodo Reset (). Il metodo di ripristino modifica il valore booleano in falso.

Di seguito è riportata la sintassi della chiamata del metodo di ripristino.

manualResetEvent.Reset();

Dobbiamo chiamare immediatamente il metodo Reset dopo aver chiamato il metodo Set se vogliamo inviare il segnale ai thread più volte.


7

Sì. Questo è assolutamente corretto.

È possibile visualizzare ManualResetEvent come un modo per indicare lo stato. Qualcosa è acceso (Imposta) o spento (Ripristina). Un evento con una certa durata. Qualsiasi thread in attesa del verificarsi di tale stato può procedere.

Un AutoResetEvent è più paragonabile a un segnale. Un'indicazione one shot che è successo qualcosa. Un evento senza durata. In genere, ma non necessariamente, il "qualcosa" che è successo è piccolo e deve essere gestito da un singolo thread, quindi il ripristino automatico dopo che un singolo thread ha consumato l'evento.


7

Sì, è giusto.

Puoi avere un'idea usando questi due.

Se hai bisogno di dire che hai finito con un po 'di lavoro e altri (thread) in attesa di questo ora possono procedere, dovresti usare ManualResetEvent.

Se è necessario disporre dell'accesso reciproco esclusivo a qualsiasi risorsa, è necessario utilizzare AutoResetEvent.


1

Se vuoi capire AutoResetEvent e ManualResetEvent devi capire non il threading ma gli interrupt!

.NET vuole evocare la programmazione di basso livello il più distante possibile.

Un interrupt è qualcosa usato nella programmazione di basso livello che equivale a un segnale che da basso è diventato alto (o viceversa). Quando ciò accade, il programma interrompe la sua normale esecuzione e sposta il puntatore di esecuzione sulla funzione che gestisce questo evento .

La prima cosa da fare quando succede un interrupt è resettare il suo stato, perché l'hardware funziona in questo modo:

  1. un pin è collegato a un segnale e l'hardware attende che cambi (il segnale potrebbe avere solo due stati).
  2. se il segnale cambia significa che qualcosa è successo e l'hardware ha messo una variabile di memoria nello stato (e rimane così anche se il segnale cambia di nuovo).
  3. il programma nota che gli stati di cambiamento variabile e sposta l'esecuzione in una funzione di gestione.
  4. qui la prima cosa da fare, per poter riascoltare questo interrupt, è ripristinare questa variabile di memoria allo stato non accaduto.

Questa è la differenza tra ManualResetEvent e AutoResetEvent.
Se si verifica un ManualResetEvent e non lo ripristino, la prossima volta non sarò in grado di ascoltarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.