Eu li a documentação sobre isso e acho que entendo. Um AutoResetEvent
redefine quando o código passa event.WaitOne()
, mas um ManualResetEvent
não.
Isso está correto?
Eu li a documentação sobre isso e acho que entendo. Um AutoResetEvent
redefine quando o código passa event.WaitOne()
, mas um ManualResetEvent
não.
Isso está correto?
Respostas:
Sim. É como a diferença entre pedágio e porta. A ManualResetEvent
é a porta, o qual necessita de 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
executa WaitOne()
e Reset()
como uma única operação atômica.
A resposta curta é sim. A diferença mais importante é que um AutoResetEvent permitirá apenas que um único thread em espera continue. Um ManualResetEvent, por outro lado, continuará permitindo que os threads, vários ao mesmo tempo, continuem até que você peça para parar (Redefina-o).
Extraído do livro Nutshell C # 3.0, de Joseph Albahari
Encadeamento em C # - E-Book Gratuito
Um ManualResetEvent é uma variação do AutoResetEvent. Ele difere por não ser redefinido automaticamente depois que um thread é liberado em uma chamada do WaitOne e funciona como um gate: o chamado Set abre o gate, permitindo qualquer número de threads que o WaitOne passa no gate; ligar para Reset fecha o portão, fazendo com que, potencialmente, uma fila de garçons se acumule até a próxima abertura.
Pode-se simular essa funcionalidade com um campo booleano "gateOpen" (declarado com a palavra-chave volátil) em combinação com "spin-sleeping" - verificando repetidamente o sinalizador e depois inativo por um curto período de tempo.
Às vezes, ManualResetEvents são usados para sinalizar que uma operação específica está concluída ou que a inicialização concluída de um encadeamento e está pronta para executar o trabalho.
Eu criei exemplos simples para esclarecer a compreensão da ManualResetEvent
vs AutoResetEvent
.
AutoResetEvent
: vamos supor que você tenha 3 linhas de trabalho. Se qualquer um desses threads chamar WaitOne()
todos os outros 2 threads, interromperá a execução e aguardará 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ê liga, Set()
todos os threads funcionam e aguardam o sinal. Após 1 segundo, estou enviando o segundo sinal e eles executam e aguardam ( WaitOne()
). Pense que esses caras são jogadores de times de futebol e, se um jogador disser que vou esperar até o gerente me ligar, e outros vão esperar até o gerente pedir para eles continuarem ( 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ê pressiona pela primeira Set()
vez, todos os threads desaparecem e, após 1 segundo, sinaliza para que todos os threads aguardem! Assim que você configurá-los novamente, independentemente de estarem ligando para WaitOne()
dentro, eles continuarão funcionando, pois é necessário ligar manualmente Reset()
para interromper 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 entre Árbitro / Jogador, independentemente de qualquer jogador estar ferido e esperar que outros jogadores continuem trabalhando. Se o Árbitro disser esperar ( Reset()
), todos os jogadores aguardarão 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();
}
}
}
autoResetEvent.WaitOne()
é similar a
try
{
manualResetEvent.WaitOne();
}
finally
{
manualResetEvent.Reset();
}
como uma operação atômica
OK, normalmente não é uma boa prática adicionar 2 respostas no mesmo segmento, mas eu não queria editar / excluir minha resposta anterior, pois ela pode ajudar de outra maneira.
Agora, criei, abaixo, um snippet de aplicativo de console muito mais abrangente e fácil de entender.
Apenas execute os exemplos em dois consoles diferentes e observe o comportamento. Você terá uma idéia muito mais clara do que está acontecendo nos bastidores.
Evento de redefinição 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 redefinição 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);
}
}
}
AutoResetEvent mantém uma variável booleana na memória. Se a variável booleana for falsa, ela bloqueia o encadeamento e, se a variável booleana for verdadeira, ela desbloqueia o encadeamento.
Quando instanciamos um objeto AutoResetEvent, passamos o valor padrão do valor booleano no construtor. Abaixo está a sintaxe de instanciar um objeto AutoResetEvent.
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
Método WaitOne
Este método bloqueia o segmento atual e aguarda o sinal por outro segmento. O método WaitOne coloca o thread atual em um estado de suspensão do thread. O método WaitOne retorna true se receber o sinal, caso contrário, retorna false.
autoResetEvent.WaitOne();
A segunda sobrecarga do método WaitOne aguarda o número especificado de segundos. Se ele não receber nenhum segmento 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");
}
Chamamos o método WaitOne passando os 2 segundos como argumentos. No loop while, aguarda o sinal por 2 segundos e continua seu trabalho. Quando o thread recebe o sinal, o WaitOne retorna true, sai do loop e imprime o "Thread got signal".
Definir método
O método AutoResetEvent Set enviou o sinal para o thread em espera para continuar seu trabalho. Abaixo está a sintaxe de chamar o método Set.
autoResetEvent.Set();
ManualResetEvent mantém uma variável booleana na memória. Quando a variável booleana é falsa, ela bloqueia todos os segmentos e, quando a variável booleana é verdadeira, desbloqueia todos os segmentos.
Quando instanciamos um ManualResetEvent, inicializamos com o valor booleano padrão.
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
No código acima, inicializamos o ManualResetEvent com valor falso, ou seja, todos os threads que chamam o método WaitOne serão bloqueados até que algum thread chame o método Set ().
Se inicializarmos ManualResetEvent com valor verdadeiro, todos os threads que chamam o método WaitOne não serão bloqueados e liberados para prosseguir.
Método WaitOne
Este método bloqueia o segmento atual e aguarda o sinal por outro segmento. Retorna true se receber um sinal, caso contrário, retornará false.
Abaixo está a sintaxe de chamar o método WaitOne.
manualResetEvent.WaitOne();
Na segunda sobrecarga do método WaitOne, podemos especificar o intervalo de tempo até o encadeamento atual aguardar o sinal. Se dentro do tempo interno, ele não recebe um sinal, retorna falso e passa para a próxima linha de método.
Abaixo está a sintaxe 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 objeto manualResetEvent não receber um sinal entre 5 segundos, defina a variável isSignalled como false.
Método Set
Este método é usado para enviar o sinal para todos os threads em espera. Método Set () defina a variável booleana do objeto ManualResetEvent como true. Todos os threads em espera são desbloqueados e prosseguem.
Abaixo está a sintaxe de chamar o método Set ().
manualResetEvent.Set();
Método de redefinição
Depois que chamamos o método Set () no objeto ManualResetEvent, seu booleano permanece verdadeiro. Para redefinir o valor, podemos usar o método Reset (). Redefinir método altere o valor booleano para false.
Abaixo está a sintaxe de chamar o método Reset.
manualResetEvent.Reset();
Devemos chamar imediatamente o método Reset depois de chamar o método Set, se queremos enviar sinal para threads várias vezes.
Sim. Isto está absolutamente correto.
Você pode ver ManualResetEvent como uma maneira de indicar o estado. Algo está ativado (definido) ou desativado (redefinido). Uma ocorrência com alguma duração. Qualquer thread que aguarde esse estado pode continuar.
Um AutoResetEvent é mais comparável a um sinal. Uma indicação instantânea de que algo aconteceu. Uma ocorrência sem qualquer duração. Normalmente, mas não necessariamente, o "algo" que aconteceu é pequeno e precisa ser tratado por um único thread - portanto, a redefinição automática após um único thread consumir o evento.
Sim está certo.
Você pode ter uma idéia usando esses dois.
Se você precisar informar que concluiu algum trabalho e outros (threads) aguardando isso agora podem continuar, use ManualResetEvent.
Se você precisar ter acesso exclusivo mútuo a qualquer recurso, use o AutoResetEvent.
Se você quiser entender AutoResetEvent e ManualResetEvent, precisará entender não o encadeamento, mas as interrupções!
O .NET quer conjurar a programação de baixo nível o mais distante possível.
Uma interrupção é algo usado na programação de baixo nível que é igual a um sinal que de baixo se tornou alto (ou vice-versa). Quando isso acontece, o programa interrompe sua execução normal e move o ponteiro de execução para a função que manipula esse evento .
A primeira coisa a fazer quando ocorre uma interrupção é redefinir seu estado, porque o hardware funciona desta maneira:
Essa é a diferença entre ManualResetEvent e AutoResetEvent.
Se um ManualResetEvent acontecer e eu não o redefinir, da próxima vez que acontecer, não poderei ouvi-lo.