В чем разница между ManualResetEvent и AutoResetEvent в .NET?

Я прочитал документацию по этому вопросу, и я думаю, что понимаю. AutoResetEvent сбрасывается, когда код проходит через event.WaitOne(), но ManualResetEvent нет.

Правильно ли это?

Ответ 1

Да. Это похоже на разницу между столовой и дверью. ManualResetEvent - это дверь, которая должна быть закрыта (reset) вручную. AutoResetEvent - это платная точка, позволяющая проезжать один автомобиль и автоматически закрываться до следующего прохождения.

Ответ 2

Представьте себе, что AutoResetEvent выполняет WaitOne() и Reset() как одну атомную операцию.

Ответ 3

Короткий ответ - да. Самое важное отличие состоит в том, что AutoResetEvent будет поддерживать только один поток ожидания. С другой стороны, ManualResetEvent будет продолжать разрешать потоки, в то время как несколько раз в то же время, продолжать, пока вы не остановите его (Reset it).

Ответ 4

Взято из С# 3.0 Краткая книга, по Джозеф Альбахари

Threading in С# - Бесплатная электронная книга

A ManualResetEvent - это вариант AutoResetEvent. Он отличается тем, что он автоматически не выполняет reset после того, как поток пропускается по вызову WaitOne и, таким образом, функционирует как ворота: вызов Set открывает ворота, позволяя любое количество потоков, которые WaitOne у ворот; вызов reset закрывает ворота, вызывая, потенциально, очередь официантов накапливаться до следующего его открытия.

Можно было бы имитировать эту функциональность с помощью логического поля "gateOpen" (объявленного с ключевым словом volatile) в сочетании с "спин-спящим" - многократно проверяя флаг, а затем спя себя на короткий период времени.

ManualResetEvents иногда используются для оповещения о завершении конкретной операции или завершении инициализации потока и готовности к выполнению работы.

Ответ 5

Я создал простые примеры для уточнения понимания ManualResetEvent против AutoResetEvent.

AutoResetEvent: предположим, у вас есть 3 рабочих потока. Если какой-либо из этих потоков вызовет WaitOne() все остальные 2 потока прекратят выполнение и будут ждать сигнала. Я предполагаю, что они используют WaitOne(). Это как; если я не работаю, никто не работает. В первом примере вы можете увидеть, что

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

Когда вы вызываете Set() все потоки будут работать и ждать сигнала. Через 1 секунду я посылаю второй сигнал, и они выполняются и ждут (WaitOne()). Представьте, что эти ребята являются игроками футбольной команды, и если один из игроков скажет, что я подожду, пока менеджер позвонит мне, а другие подождут, пока менеджер скажет им продолжить (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();
        }
    }
}

В этом примере вы можете ясно увидеть, что когда вы в первый раз нажмете Set() он пропустит все потоки, затем через 1 секунду он подаст сигнал всем потокам на ожидание! Как только вы установите их снова, независимо от того, что они вызывают WaitOne() внутри, они будут продолжать работать, потому что вы должны вручную вызвать Reset() чтобы остановить их все.

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

Это больше касается отношений Рефери/Игроки там, независимо от того, кто из игроков получил травму, и ожидание, когда другие играют, продолжит работать. Если рефери говорит "подождите" (Reset()), тогда все игроки будут ждать до следующего сигнала.

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

Ответ 6

autoResetEvent.WaitOne()

похож на

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

как атомная операция

Ответ 7

Хорошо, обычно не рекомендуется добавлять 2 ответа в один поток, но я не хотел редактировать/удалять свой предыдущий ответ, так как он может помочь по-другому.

Теперь я создал, гораздо более полный и понятный, нижеприведенный ниже фрагмент приложения для запуска.

Просто запустите примеры на двух разных консолях и наблюдайте за поведением. Вы получите гораздо более четкое представление о том, что происходит за кулисами.

Событие сброса вручную

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

}

Manual Reset Event Output

Событие автоматического сброса

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

}

Auto Reset Event Output

Ответ 8

Да. Это абсолютно правильно.

Вы можете увидеть ManualResetEvent как способ указать состояние. Что-то включено (Set) или выключено (Reset). Случай с некоторой продолжительностью. Любой поток, ожидающий этого состояния, может продолжить.

Функция AutoResetEvent более сравнима с сигналом. Один выстрел указывает на то, что что-то произошло. Случай без какой-либо продолжительности. Обычно, но не обязательно "что-то", что произошло, мало и должно обрабатываться одним потоком - следовательно, автоматический reset после того, как один поток поглотил событие.

Ответ 9

Да, это правильно.

Вы можете получить представление об использовании этих двух.

Если вам нужно сказать, что вы закончили с некоторой работой, и другие (потоки), ожидающие этого, теперь можно продолжить, вы должны использовать ManualResetEvent.

Если вам нужен взаимный эксклюзивный доступ к любому ресурсу, вы должны использовать AutoResetEvent.

Ответ 10

AutoResetEvent поддерживает логическую переменную в памяти. Если логическая переменная имеет значение false, она блокирует поток и, если логическая переменная истинна, она блокирует поток.

Когда мы создаем экземпляр объекта AutoResetEvent, мы передаем значение по умолчанию для значения boolean в конструкторе. Ниже приведен синтаксис создания экземпляра объекта AutoResetEvent.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Метод WaitOne

Этот метод блокирует текущий поток и ждет сигнала другим потоком. Метод WaitOne помещает текущий поток в состояние "Спящий поток". Метод WaitOne возвращает true, если он принимает сигнал else, возвращает false.

autoResetEvent.WaitOne();

Вторая перегрузка метода WaitOne ожидает указанного количества секунд. Если он не получает ни одной сигнальной нити, продолжается ее работа.

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

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

Мы вызвали метод WaitOne, передав в качестве аргументов 2 секунды. В цикле while он ждет сигнала в течение 2 секунд, а затем продолжает работу. Когда поток получил сигнал WaitOne, возвращает значение true и выходит из цикла и печатает сигнал "Thread got".

Установить метод

Метод AutoResetEvent Set отправил сигнал в ожидающий поток, чтобы продолжить его работу. Ниже приведен синтаксис вызова метода Set.

autoResetEvent.Set();

ManualResetEvent поддерживает булевскую переменную в памяти. Когда логическая переменная имеет значение false, она блокирует все потоки и когда логическая переменная истинна, она блокирует все потоки.

Когда мы создаем экземпляр ManualResetEvent, мы инициализируем его с использованием логического значения по умолчанию.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

В приведенном выше коде мы инициализируем ManualResetEvent с ложным значением, что означает, что все потоки, вызывающие метод WaitOne, будут блокироваться до тех пор, пока какой-либо поток не вызовет метод Set().

Если мы инициализируем ManualResetEvent с истинным значением, все потоки, вызывающие метод WaitOne, не будут блокироваться и освобождаться дальше.

Метод WaitOne

Этот метод блокирует текущий поток и ждет сигнала другим потоком. Он возвращает true, если он получает сигнал else, возвращает false.

Ниже приведен синтаксис вызова метода WaitOne.

manualResetEvent.WaitOne();

Во второй перегрузке метода WaitOne мы можем указать временной интервал до тех пор, пока текущий поток не будет ждать сигнала. Если во внутреннем времени он не получает сигнал, он возвращает false и переходит в следующую строку метода.

Ниже приведен синтаксис вызова метода WaitOne с временным интервалом.

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

Мы указываем 5 секунд в методе WaitOne. Если объект manualResetEvent не получает сигнал между 5 секундами, он устанавливает переменную isSignalled в значение false.

Установить метод

Этот метод используется для отправки сигнала во все ожидающие потоки. Метод Set() устанавливает для булевой переменной объекта ManualResetEvent значение true. Все ожидающие потоки разблокируются и продолжаются дальше.

Ниже приведен синтаксис вызова метода Set().

manualResetEvent.Set();

Reset Метод

Как только мы вызываем метод Set() в объекте ManualResetEvent, его логическое значение остается верным. Для reset можно использовать метод Reset(). reset измените логическое значение на false.

Ниже приведен синтаксис вызова метода reset.

manualResetEvent.Reset();

Мы должны немедленно вызвать метод reset после вызова метода Set, если мы хотим отправить сигнал в потоки несколько раз.

Ответ 11

Если вы хотите понять AutoResetEvent и ManualResetEvent, вам нужно понимать не многопоточность, а прерывания!

.NET хочет вызвать низкоуровневое программирование как можно дальше.

Прерывания - это то, что используется в низкоуровневом программировании, что соответствует сигналу, который с низкого уровня стал высоким (или наоборот). Когда это происходит, программа прерывает нормальное выполнение и перемещает указатель выполнения на функцию, которая обрабатывает это событие.

Первое, что нужно сделать, когда происходит прерывание, это сбросить его состояние, потому что оборудование работает следующим образом:

  1. контакт подключен к сигналу, и аппаратное обеспечение ожидает его изменения (сигнал может иметь только два состояния).
  2. если изменение сигнала означает, что что-то произошло, и аппаратное обеспечение поместило переменную памяти в состояние произошло (и оно останется таким, даже если сигнал снова изменится).
  3. программа замечает, что переменные изменяют состояния и перемещают выполнение в функцию обработки.
  4. здесь первое, что нужно сделать, чтобы снова прослушать это прерывание, - сбросить эту переменную памяти в состояние "не произошло".

В этом разница между ManualResetEvent и AutoResetEvent.
Если произойдет событие ManualResetEvent, и я не сброслю его, в следующий раз, когда это произойдет, я не смогу его прослушать.