Вопрос

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

Обработка потоков в C # - Бесплатная электронная книга

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

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

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

Я привел простые примеры, чтобы прояснить понимание 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();
        }
    }
}

autoResetEvent.WaitOne()

похож на

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

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

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

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

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

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

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

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

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

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

Да, это так.

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

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

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

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

Когда мы создаем экземпляр объекта AutoResetEvent, мы передаем значение по умолчанию boolean value в конструкторе.Ниже приведен синтаксис создания экземпляра объекта 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, выходит из цикла и выводит сообщение "Поток получил сигнал".

Метод установки

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

autoResetEvent.Set();

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

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

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

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

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

Метод WaitOne

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

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

manualResetEvent.WaitOne();

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

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

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

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

Метод установки

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

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

manualResetEvent.Set();

Способ сброса

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

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

manualResetEvent.Reset();

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top