¿Cuál es la diferencia entre ManualResetEvent y AutoResetEvent en .NET?

Respuestas:

920

Si. Es como la diferencia entre un peaje y una puerta. Esta ManualResetEventes la puerta, que debe cerrarse (reiniciarse) manualmente. El AutoResetEventes una cabina de peaje, lo que permite un coche a pasar y cerrar automáticamente antes de que el siguiente puede pasar.

Dan Goldstein
fuente
166
Esa es una gran analogía.
twk
Peor aún, no espere demasiado para configurar ARE en WaitOne, o se restablecerá mientras tanto. Tenía muchos hilos abandonados con eso.
Oliver Friedrich
24
O como una puerta y un torniquete.
Constantin
99
Oh, por eso se les llama como son.
Arlen Beiler
1
@DanGoldstein bien, ya que esto no está cerrado y en caso de que alguien más lo quiera: msdn.microsoft.com/en-us/library/…
Phil N DeBlanc
124

Solo imagine que se AutoResetEventejecuta WaitOne()y Reset()como una sola operación atómica.

Michael Damatov
fuente
16
Excepto que si ejecutó WaitOne y Reset como una sola operación atómica en un evento ManualResetEvent, todavía haría algo diferente a un AutoResetEvent. El ManualResetEvent libera todos los hilos en espera al mismo tiempo, mientras que AutoResetEvent garantiza que solo se liberará un hilo en espera.
Martin Brown
55

La respuesta corta es sí. La diferencia más importante es que un AutoResetEvent solo permitirá que continúe un solo hilo de espera. Un ManualResetEvent, por otro lado, seguirá permitiendo que los subprocesos, varios al mismo tiempo, continúen hasta que le pidas que se detenga (Restablezca).

Martin Brown
fuente
36

Tomado del libro C # 3.0 Nutshell, por Joseph Albahari

Enhebrado en C # - Libro electrónico gratuito

Un ManualResetEvent es una variación de AutoResetEvent. Se diferencia en que no se restablece automáticamente después de que se deja pasar un hilo en una llamada WaitOne, y por lo tanto funciona como una puerta: al llamar a Set se abre la puerta, permitiendo cualquier cantidad de hilos por los que pasa WaitOne en la puerta; Al llamar a Reset, se cierra la puerta y, potencialmente, se acumula una cola de camareros hasta que se abre de nuevo.

Se podría simular esta funcionalidad con un campo booleano "gateOpen" (declarado con la palabra clave volátil) en combinación con "spin-sleeping", verificando repetidamente la bandera y luego durmiendo durante un corto período de tiempo.

En ocasiones, ManualResetEvents se usa para indicar que una operación en particular está completa o que la inicialización de un subproceso está lista y lista para realizar el trabajo.


fuente
19

He creado sencillos ejemplos para aclarar la comprensión de ManualResetEventfrente AutoResetEvent.

AutoResetEvent: supongamos que tiene 3 hilos de trabajo. Si alguno de esos hilos llamará a WaitOne()los otros 2 hilos, detendrá la ejecución y esperará la señal. Estoy asumiendo que están usando WaitOne(). Es como; Si no trabajo, nadie trabaja. En el primer ejemplo puedes ver que

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

Cuando llame, Set()todos los hilos funcionarán y esperarán la señal. Después de 1 segundo, estoy enviando una segunda señal y se ejecutan y esperan ( WaitOne()). Piensa que estos muchachos son jugadores del equipo de fútbol y si un jugador dice que esperaré hasta que el gerente me llame, y otros esperarán hasta que el gerente les diga que continúen ( 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();
        }
    }
}

En este ejemplo, puede ver claramente que cuando golpea por primera Set()vez, dejará ir todos los hilos, ¡luego de 1 segundo le indicará a todos los hilos que esperen! Tan pronto como los configure de nuevo, independientemente de que WaitOne()llamen dentro, seguirán ejecutándose porque debe llamar manualmente Reset()para detenerlos a todos.

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

Se trata más de la relación Árbitro / Jugadores allí, independientemente de si alguno de los jugadores está lesionado y esperar a que otros continúen trabajando. Si el Árbitro dice esperar ( Reset()), todos los jugadores esperarán hasta la próxima señal.

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
fuente
13

autoResetEvent.WaitOne()

es parecido a

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

como una operación atómica

vezenkov
fuente
Esto es solo conceptualmente correcto, pero no prácticamente. Entre WaitOne y Reset puede ocurrir un cambio de contexto; Esto puede conducir a errores sutiles.
hofingerandi
2
¿Podrías votarlo ahora? Nadie prácticamente hará el segundo bloque de código aquí, es cuestión de entender la diferencia.
vezenkov
11

OK, normalmente no es una buena práctica agregar 2 respuestas en el mismo hilo, pero no quería editar / eliminar mi respuesta anterior, ya que puede ayudar de otra manera.

Ahora, he creado, a continuación, un fragmento de aplicación de consola mucho más completo y fácil de entender.

Simplemente ejecute los ejemplos en dos consolas diferentes y observe el comportamiento. Tendrás una idea mucho más clara de lo que está sucediendo detrás de escena.

Evento de reinicio manual

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

}

Salida de evento de reinicio manual

Evento de reinicio automático

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

}

Salida de evento de reinicio automático

Teoman shipahi
fuente
esta era la mejor manera de entender todo, copiar el código y corrió todo mientras se cambia algunas cosas, entendido bien ahora
JohnChris
8

AutoResetEvent mantiene una variable booleana en la memoria. Si la variable booleana es falsa, entonces bloquea el hilo y si la variable booleana es verdadera, desbloquea el hilo.

Cuando instanciamos un objeto AutoResetEvent, pasamos el valor predeterminado del valor booleano en el constructor. A continuación se muestra la sintaxis de instanciar un objeto AutoResetEvent.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Método WaitOne

Este método bloquea el hilo actual y espera la señal de otro hilo. El método WaitOne coloca el subproceso actual en un estado de subproceso de suspensión. El método WaitOne devuelve verdadero si recibe la señal; de lo contrario, devuelve falso.

autoResetEvent.WaitOne();

La segunda sobrecarga del método WaitOne espera el número de segundos especificado. Si no recibe ningún hilo de señal, continúa su trabajo.

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

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

Llamamos al método WaitOne pasando los 2 segundos como argumentos. En el ciclo while, espera la señal durante 2 segundos y luego continúa su trabajo. Cuando el hilo recibió la señal, WaitOne regresa verdadero y sale del bucle e imprime el "Hilo tiene señal".

Establecer método

El método AutoResetEvent Set envió la señal al subproceso en espera para continuar su trabajo. A continuación se muestra la sintaxis de llamar al método Set.

autoResetEvent.Set();

ManualResetEvent mantiene una variable booleana en la memoria. Cuando la variable booleana es falsa, bloquea todos los hilos y cuando la variable booleana es verdadera, desbloquea todos los hilos.

Cuando instanciamos un ManualResetEvent, lo inicializamos con un valor booleano predeterminado.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

En el código anterior, inicializamos el ManualResetEvent con un valor falso, lo que significa que todos los hilos que llaman al método WaitOne se bloquearán hasta que algún hilo llame al método Set ().

Si inicializamos ManualResetEvent con valor verdadero, todos los hilos que llaman al método WaitOne no se bloquearán y podrán continuar.

Método WaitOne

Este método bloquea el hilo actual y espera la señal de otro hilo. Devuelve verdadero si recibe una señal; de lo contrario, devuelve falso.

A continuación se muestra la sintaxis de llamar al método WaitOne.

manualResetEvent.WaitOne();

En la segunda sobrecarga del método WaitOne, podemos especificar el intervalo de tiempo hasta que el hilo actual espere la señal. Si dentro del tiempo interno, no recibe una señal, devuelve falso y pasa a la siguiente línea de método.

A continuación se muestra la sintaxis de llamar al método WaitOne con intervalo de tiempo.

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

Hemos especificado 5 segundos en el método WaitOne. Si el objeto manualResetEvent no recibe una señal entre 5 segundos, establece la variable isSignalled en false.

Establecer método

Este método se utiliza para enviar la señal a todos los hilos en espera. El método Set () establece la variable booleana del objeto ManualResetEvent en true. Todos los hilos en espera se desbloquean y continúan.

A continuación se muestra la sintaxis de llamar al método Set ().

manualResetEvent.Set();

Método de reinicio

Una vez que llamamos al método Set () en el objeto ManualResetEvent, su valor booleano permanece verdadero. Para restablecer el valor podemos usar el método Reset (). El método de reinicio cambia el valor booleano a falso.

A continuación se muestra la sintaxis de llamar al método Reset.

manualResetEvent.Reset();

Debemos llamar inmediatamente al método Reset después de llamar al método Set si queremos enviar la señal a los hilos varias veces.

Masoud Siahkali
fuente
7

Si. Esto es absolutamente correcto.

Puede ver ManualResetEvent como una forma de indicar el estado. Algo está encendido (Set) o apagado (Reset). Una ocurrencia con cierta duración. Cualquier subproceso que espere que ocurra ese estado puede continuar.

Un AutoResetEvent es más comparable a una señal. Una indicación de un disparo de que algo ha sucedido. Una ocurrencia sin ninguna duración. Por lo general, pero no necesariamente, el "algo" que ha sucedido es pequeño y debe ser manejado por un solo subproceso, de ahí el restablecimiento automático después de que un solo subproceso haya consumido el evento.

Booz
fuente
7

Si, eso es correcto.

Puede hacerse una idea mediante el uso de estos dos.

Si necesita saber que ha terminado con algún trabajo y otros (subprocesos) esperando que esto pueda continuar, debe usar ManualResetEvent.

Si necesita tener acceso exclusivo mutuo a cualquier recurso, debe usar AutoResetEvent.

Swapnil Patil
fuente
1

Si desea comprender AutoResetEvent y ManualResetEvent, debe comprender no enhebrar sino interrumpir.

.NET quiere conjurar programación de bajo nivel lo más distante posible.

Una interrupción es algo que se usa en la programación de bajo nivel que equivale a una señal que desde baja se volvió alta (o viceversa). Cuando esto sucede, el programa interrumpe su ejecución normal y mueve el puntero de ejecución a la función que maneja este evento .

Lo primero que debe hacer cuando ocurre una interrupción es restablecer su estado, ya que el hardware funciona de esta manera:

  1. un pin está conectado a una señal y el hardware escucha que cambie (la señal podría tener solo dos estados).
  2. si la señal cambia significa que algo sucedió y el hardware puso una variable de memoria en el estado que sucedió (y se mantiene así incluso si la señal cambia nuevamente).
  3. el programa nota que la variable cambia de estado y mueve la ejecución a una función de manejo.
  4. Aquí, lo primero que debe hacer, para poder volver a escuchar esta interrupción, es restablecer esta variable de memoria al estado no sucedido.

Esta es la diferencia entre ManualResetEvent y AutoResetEvent.
Si sucede un ManualResetEvent y no lo reinicio, la próxima vez que ocurra no podré escucharlo.

princio
fuente