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

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

Это верно?

Пусть этот av поможет понять разницу youtube.com/watch?v=xaaRBh07N34

Vinod Srivastav 30.09.2018 15:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
545
1
179 617
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

Ответ принят как подходящий

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

Хуже того, не ждите слишком долго, чтобы установить ARE на WaitOne, или тем временем он будет сброшен. Было много заброшенных тем с этим.

Oliver Friedrich 22.12.2008 12:53

Или как дверь и турникет.

Constantin 06.01.2009 22:01

Ой, поэтому их называют такими, какие они есть.

Arlen Beiler 05.10.2012 15:09

@DanGoldstein ну, так как это не закрыто и на случай, если кто-то еще захочет: msdn.microsoft.com/en-us/library/…

Phil N DeBlanc 17.04.2018 23:04

Вы только что объяснили разницу между словами «автоматический» и «ручной».

Марк Павлович 01.04.2020 22:56

ManualResetEvent похож на дверь, все потоки в ситуации waitone будут освобождены после вызова set до тех пор, пока не будет выполнен сброс в следующий момент.

abramhum 03.09.2020 08:19

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

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

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

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

За исключением того, что если вы выполнили WaitOne и Reset как одну атомарную операцию с событием ManualResetEvent, он все равно будет делать что-то отличное от AutoResetEvent. ManualResetEvent освобождает все ожидающие потоки одновременно, тогда как AutoResetEvent гарантирует освобождение только одного ожидающего потока.

Martin Brown 27.08.2016 13:18

Taken from C# 3.0 Nutshell book, by Joseph Albahari

Многопоточность в C# - Бесплатная электронная книга

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

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

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

Да все верно.

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

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

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

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

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

Это правильно только концептуально, но не практически. Между WaitOne и Reset может произойти переключение контекста; это может привести к незаметным ошибкам.

hofingerandi 13.10.2015 09:26

Не могли бы вы проголосовать за это сейчас? Второй блок кода здесь практически никто делать не будет, дело в понимании разницы.

vezenkov 13.10.2015 11:06

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

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

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Метод WaitOne

Этот метод блокирует текущий поток и ожидает сигнала от другого потока. Метод WaitOne переводит текущий поток в состояние спящего потока. Метод WaitOne возвращает true, если получает сигнал, иначе возвращает 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 возвращает истину, выходит из цикла и печатает «Поток получил сигнал».

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

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

autoResetEvent.Set();

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

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

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

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

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

Метод WaitOne

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

Ниже приведен синтаксис вызова метода 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, его логическое значение остается истинным. Чтобы сбросить значение, мы можем использовать метод Reset (). Метод сброса изменяет логическое значение на false.

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

manualResetEvent.Reset();

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

Хорошо, обычно не рекомендуется добавлять 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

это был лучший способ все это понять, скопировал код и запустил все, изменив несколько вещей, теперь хорошо понял

JohnChris 22.05.2019 14:02

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

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

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

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

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

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

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