Qual é a diferença entre ManualResetEvent e AutoResetEvent no .NET?

Eu li a documentação sobre isso e acho que entendi. Um AutoResetEvent redefine quando o código passa por event.WaitOne() , mas um ManualResetEvent não.

Isso está correto?

Sim. É como a diferença entre um pedágio e uma porta. O ManualResetEvent é a porta, que precisa ser fechada (reset) manualmente. O AutoResetEvent é um pedágio, permitindo que um carro passe e feche automaticamente antes que o próximo possa passar.

Imagine que o AutoResetEvent execute WaitOne() e Reset() como uma única operação atômica.

A resposta curta é sim. A diferença mais importante é que um AutoResetEvent permitirá que apenas um único segmento em espera continue. Um ManualResetEvent, por outro lado, continuará permitindo que os encadeamentos, vários ao mesmo tempo, continuem até que você diga para parar (Redefinir).

Retirado de C # 3.0 Nutshell book, por Joseph Albahari

Enfiando em C # – E-Book Grátis

Um ManualResetEvent é uma variação do AutoResetEvent. Ele difere na medida em que não é redefinido automaticamente depois que um thread é liberado em uma chamada WaitOne e, portanto, funciona como um gate: chamar Set abre o gate, permitindo qualquer número de threads que WaitOne no portão através de; Chamar Redefinir fecha o gate, fazendo com que, potencialmente, uma fila de garçons se acumule até a próxima abertura.

É possível simular essa funcionalidade com um campo “gateOpen” booleano (declarado com a palavra-chave volátil) em combinação com “spin-sleeping” – verificando repetidamente o sinalizador e depois dormindo por um curto período de tempo.

ManualResetEvents às vezes são usados ​​para sinalizar que uma determinada operação está completa ou que a boot concluída de um segmento está pronta para executar o trabalho.

Eu criei exemplos simples para esclarecer a compreensão do ManualResetEvent vs AutoResetEvent.

AutoResetEvent: vamos supor que você tenha 3 threads de trabalho. Se algum desses threads chamar WaitOne (), todos os outros 2 threads interromperão a execução e aguardarão o sinal. Estou assumindo que eles estão usando WaitOne (). É como; se eu não trabalhar, ninguém trabalha. No primeiro exemplo, você pode ver que

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

Quando você chama Set (); todos os segmentos funcionarão e aguardarão o sinal. Depois de 1 segundo estou enviando o segundo sinal e eles executam e aguardam (WaitOne ();). Pense sobre esses caras são jogadores de time de futebol e se um jogador disser que vou esperar até que o gerente me chame, e outros vão esperar até que o gerente diga para continuar (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(); } } } 

Neste exemplo você pode ver claramente que quando você bateu pela primeira vez Set (); ele deixará todos os threads partirem, depois de 1 segundo ele sinaliza a todos os segmentos para aguardarem! Assim que você configurá-los novamente, independentemente de eles estarem chamando WaitOne () dentro, eles continuarão executando porque você tem que chamar manualmente Reset () para parar todos eles.

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

É mais sobre a relação Árbitro / Jogadores, independentemente de qualquer um dos jogadores estar lesionado e esperar que os outros jogadores continuem a trabalhar. Se o Árbitro diz esperar (Reset ();) então todos os jogadores irão esperar até o próximo sinal.

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

Sim está certo.

Você pode ter uma ideia pelo uso desses dois.

Se você precisar dizer que terminou algum trabalho e outros (threads) esperando por isso, você pode usar ManualResetEvent.

Se você precisa ter access exclusivo mútuo a qualquer recurso, use o AutoResetEvent.

autoResetEvent.WaitOne()

é similar a

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

como uma operação atômica

Sim. Isso está absolutamente correto.

Você poderia ver ManualResetEvent como uma maneira de indicar o estado. Algo está ligado (Set) ou desligado (Reset). Uma ocorrência com alguma duração. Qualquer encadeamento aguardando que esse estado aconteça pode continuar.

Um AutoResetEvent é mais comparável a um sinal. Uma indicação de um tiro que algo aconteceu. Uma ocorrência sem qualquer duração. Normalmente, mas não necessariamente, o “algo” que aconteceu é pequeno e precisa ser manipulado por um único thread – daí a reconfiguração automática após um único thread ter consumido o evento.

OK, normalmente não é uma boa prática adicionar 2 respostas no mesmo tópico, mas eu não queria editar / excluir minha resposta anterior, pois ela pode ajudar de outra maneira.

Agora, criei um snippet de aplicativo de console muito mais abrangente e fácil de entender, de executar para aprender, abaixo.

Basta executar os exemplos em dois consoles diferentes e observar o comportamento. Você terá uma ideia muito mais clara do que está acontecendo nos bastidores.

Evento de reboot 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); } } } 

Saída de evento de redefinição manual

Evento de reboot automática

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

Saída de evento de reinicialização automática

AutoResetEvent mantém uma variável booleana na memory. Se a variável booleana é falsa, ela bloqueia o encadeamento e, se a variável booleana for verdadeira, ele desbloqueia o encadeamento.

Quando instanciamos um object AutoResetEvent, passamos o valor padrão de valor booleano no construtor. Abaixo está a syntax de instanciar um object AutoResetEvent.

 AutoResetEvent autoResetEvent = new AutoResetEvent(false); 

Método WaitOne

Esse método bloqueia o thread atual e aguarda o sinal por outro thread. WaitOne método coloca o segmento atual em um estado de thread de suspensão. O método WaitOne retorna true se receber o sinal else retorna false.

 autoResetEvent.WaitOne(); 

A segunda sobrecarga do método WaitOne aguarda o número especificado de segundos. Se não obtiver nenhum fio de sinal continua seu trabalho.

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

Nós chamamos o método WaitOne passando os 2 segundos como argumentos. No loop while, ele espera pelo sinal por 2 segundos e continua seu trabalho. Quando o thread recebe o sinal WaitOne retorna true e sai do loop e imprime o “Thread got signal”.

Definir método

O método AutoResetEvent Set enviou o sinal ao thread em espera para continuar seu trabalho. Abaixo está a syntax de chamar o método Set.

 autoResetEvent.Set(); 

ManualResetEvent mantém uma variável booleana na memory. Quando a variável booleana é falsa, ela bloqueia todos os encadeamentos e, quando a variável booleana é verdadeira, ela desbloqueia todos os encadeamentos.

Quando instanciamos um ManualResetEvent, inicializamos com valor booleano padrão.

 ManualResetEvent manualResetEvent = new ManualResetEvent(false); 

No código acima, inicializamos o ManualResetEvent com valor false, o que significa que todos os encadeamentos que chamam o método WaitOne bloquearão até que algum encadeamento chame o método Set ().

Se inicializarmos ManualResetEvent com o valor true, todos os threads que chamam o método WaitOne não serão bloqueados e liberados para prosseguir.

Método WaitOne

Esse método bloqueia o thread atual e aguarda o sinal por outro thread. Retorna true se recebe um sinal de outra forma retorna false.

Abaixo está a syntax de chamar o método WaitOne.

 manualResetEvent.WaitOne(); 

Na segunda sobrecarga do método WaitOne, podemos especificar o intervalo de tempo até que o segmento atual espere pelo sinal. Se dentro do tempo interno, ele não receber um sinal, ele retornará false e entrará na próxima linha do método.

Abaixo está a syntax de chamar o método WaitOne com intervalo de tempo.

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

Nós especificamos 5 segundos no método WaitOne. Se o object manualResetEvent não receber um sinal entre 5 segundos, defina a variável isSignalled como false.

Definir método

Este método é usado para enviar o sinal para todos os threads em espera. O método Set () define a variável booleana do object ManualResetEvent como true. Todos os tópicos em espera são desbloqueados e prosseguem.

Abaixo está a syntax de chamar o método Set ().

 manualResetEvent.Set(); 

Repor Método

Uma vez que chamamos o método Set () no object ManualResetEvent, seu booleano permanece verdadeiro. Para redefinir o valor, podemos usar o método Reset (). Redefinir método altera o valor booleano para false.

Abaixo está a syntax de chamar o método Reset.

 manualResetEvent.Reset(); 

Devemos chamar imediatamente o método Reset depois de chamar o método Set, se quisermos enviar o sinal para threads várias vezes.