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

Respostas:

920

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.

Dan Goldstein
fonte
166
Essa é uma ótima analogia.
twk 30/09/08
Pior ainda, não espere muito tempo para definir o ARE como o WaitOne, ou ele será redefinido nesse meio tempo. Tinha muitos tópicos abandonados com isso.
Oliver Friedrich
24
Ou como uma porta e uma catraca.
Constantin
9
Oh, é por isso que eles são nomeados como são.
Arlen Beiler
1
@DanGoldstein bem, uma vez que este não é fechado e no caso de alguém o quer: msdn.microsoft.com/en-us/library/...
Phil N DeBlanc
124

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

Michael Damatov
fonte
16
Exceto que, se você executasse o WaitOne e o Reset como uma única operação atômica em um evento ManualResetEvent, ele ainda faria algo diferente de um AutoResetEvent. O ManualResetEvent libera todos os threads em espera ao mesmo tempo, enquanto o AutoResetEvent garante liberar apenas um thread em espera.
Martin Brown
55

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

Martin Brown
fonte
36

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.


fonte
19

Eu criei exemplos simples para esclarecer a compreensão da ManualResetEventvs 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();
        }
    }
}
Teoman shipahi
fonte
13

autoResetEvent.WaitOne()

é similar a

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

como uma operação atômica

vezenkov
fonte
Isso é apenas conceitualmente correto, mas não praticamente. Entre o WaitOne e o Reset, uma opção de contexto pode ocorrer; isso pode levar a erros sutis.
hofingerandi
2
Você poderia votar agora? Ninguém praticamente fará o segundo bloco de código aqui, é uma questão de entender a diferença.
vezenkov 13/10/2015
11

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

}

Saída de evento de redefinição manual

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

}

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

Teoman shipahi
fonte
esta era a melhor maneira de entender tudo isso, copiou o código e correu tudo enquanto mudando algumas coisas, entendeu bem agora
JohnChris
8

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.

Masoud Siahkali
fonte
7

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.

Boaz
fonte
7

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.

Swapnil Patil
fonte
1

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:

  1. um pino está conectado a um sinal e o hardware escuta a alteração (o sinal pode ter apenas dois estados).
  2. se o sinal mudar significa que algo aconteceu e o hardware colocou uma variável de memória no estado que aconteceu (e permanece assim mesmo que o sinal mude novamente).
  3. o programa percebe que a variável muda de estado e move a execução para uma função de manipulação.
  4. aqui, a primeira coisa a fazer, para poder escutar novamente essa interrupção, é redefinir essa variável de memória para o estado que não aconteceu.

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.

princio
fonte