c# .net multithreading autoresetevent manualresetevent

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



multithreading (10)

He leído la documentación sobre esto y creo que entiendo. Un AutoResetEvent restablece cuando el código pasa a través del event.WaitOne() , pero un ManualResetEvent no lo hace.

¿Es esto correcto?


Tomado de C # 3.0 libro de cáscara de nuez, por Joseph Albahari

Roscado en C # - E-libro gratis

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 de WaitOne, y por lo tanto funciona como una puerta: la llamada Set abre la puerta, permitiendo cualquier cantidad de hilos que WaitOne pase por la puerta; Al reiniciar se cierra la puerta, lo que hace que, potencialmente, una cola de camareros se acumule hasta que se abra de nuevo.

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

ManualResetEvents a veces se usa para indicar que una operación en particular está completa, o que la rosca se ha completado y está lista para realizar el trabajo.


Creé ejemplos simples para aclarar la comprensión de ManualResetEvent vs AutoResetEvent.

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

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

Cuando llamas a Set (); Todos los hilos funcionarán y esperarán la señal. Después de 1 segundo, estoy enviando la segunda señal y se ejecutan y esperan (WaitOne ();). Piense que estos jugadores son jugadores de fútbol y si un jugador dice que esperaré hasta que el gerente me llame, y otros esperarán hasta que el administrador les diga que continúen (Establecer ();)

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, puedes ver claramente que cuando primero presionas Set (); dejará ir todos los hilos, luego, después de 1 segundo, señala todos los hilos para que esperen! Tan pronto como los vuelva a configurar, independientemente de que estén llamando a WaitOne () desde dentro, se seguirán ejecutando porque tiene que llamar manualmente a Reset () para detenerlos a todos.

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

Es más sobre la relación del árbitro / jugadores allí, independientemente de que alguno de los jugadores esté lesionado y esperar a que otros jugadores continúen trabajando. Si el árbitro dice esperar (Reiniciar ();), todos los jugadores esperarán hasta la siguiente 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(); } } }


De acuerdo, normalmente no es una buena práctica agregar 2 respuestas en el mismo hilo, pero no quise editar / borrar mi respuesta anterior, ya que puede ayudar de otra manera.

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

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

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


Imagine que AutoResetEvent ejecuta WaitOne() y Reset() como una sola operación atómica.


La respuesta corta es sí. La diferencia más importante es que AutoResetEvent solo permitirá que un solo hilo en espera continúe. Un ManualResetEvent en la otra mano seguirá permitiendo que los hilos, varios al mismo tiempo, continúen hasta que le digas que se detenga (restablézcalo).


Sí, eso es correcto.

Usted puede tener una idea por el uso de estos dos.

Si necesita decir que ya ha terminado con algunos trabajos y otros (subprocesos) en espera de que esto pueda continuar, debe usar ManualResetEvent.

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


Sí. Es como la diferencia entre una cabina de peaje y una puerta. ManualResetEvent es la puerta, que debe cerrarse (restablecerse) manualmente. AutoResetEvent es una cabina de peaje, que permite que pase un auto y se cierra automáticamente antes de que el siguiente pueda pasar.


Sí. Esto es absolutamente correcto.

Podría ver ManualResetEvent como una forma de indicar el estado. Algo está activado (Establecer) o desactivado (Restablecer). Una ocurrencia con cierta duración. Cualquier subproceso que espera que ese estado suceda 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 duración alguna. Por lo general, pero no necesariamente, el "algo" que ha ocurrido es pequeño y debe ser manejado por un solo subproceso; por lo tanto, el restablecimiento automático después de que un solo subproceso haya consumido el evento.


autoResetEvent.WaitOne()

es parecido a

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

como una operación atómica


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

Cuando creamos una instancia de un objeto AutoResetEvent, pasamos el valor predeterminado de 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 pone el hilo actual en un estado de hilo de suspensión. El método WaitOne devuelve true si recibe la señal o devuelve false.

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 bucle while, espera la señal durante 2 segundos y luego continúa su trabajo. Cuando el subproceso recibió la señal, WaitOne devuelve verdadero y sale del bucle e imprime "El subproceso que recibió la señal".

Método de ajuste

El método AutoResetEvent Set envió la señal al hilo en espera para continuar con 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 subprocesos y cuando la variable booleana es verdadera, desbloquea todos los subprocesos.

Cuando creamos una instancia de ManualResetEvent, lo inicializamos con el valor booleano predeterminado.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

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

Si inicializamos ManualResetEvent con valor verdadero, todos los subprocesos que invocan el método WaitOne no se bloquearán y estarán libres para continuar.

Método WaitOne

Este método bloquea el hilo actual y espera la señal de otro hilo. Devuelve true si recibe una señal o devuelve false.

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 un 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.

Método de ajuste

Este método se utiliza para enviar la señal a todos los hilos en espera. 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 del método Set () de llamada.

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 (). Método de reinicio cambia el valor booleano a falso.

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

manualResetEvent.Reset();

Debemos llamar inmediatamente al método de reinicio después de llamar al método de ajuste si queremos enviar señales a los hilos varias veces.