Яка різниця між ManualResetEvent і AutoResetEvent в .NET?


532

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

Це правильно?


2
Нехай цей ав допоможе зрозуміти різницю youtube.com/watch?v=xaaRBh07N34
Вінод Срівастав

Відповіді:


920

Так. Це як різниця між платною доріжкою та дверима. ManualResetEventЄ двері, яка повинна бути закрита (скидання) вручну. Це AutoResetEventплатний будинок, що дозволяє проїхати одному авто і автоматично закритись, перш ніж може пройти наступний.


166
Це чудова аналогія.
twk

Ще гірше, не чекайте, поки довго встановити ARE на WaitOne, інакше воно тим часом буде скинуто. З цим було багато покинутих ниток.
Олівер Фрідріх

24
Або як двері та турнікет.
Костянтин

9
О, саме тому їх називають такими, якими вони є.
Арлен Бейлер

1
@DanGoldstein добре, оскільки це не закрито, і якщо хтось цього захоче: msdn.microsoft.com/en-us/library/…
Phil N DeBlanc

124

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


16
За винятком того, що якщо ви виконали WaitOne та Reset як одну атомну операцію на події ManualResetEvent, вона все одно зробить щось інше, ніж AutoResetEvent. ManualResetEvent випускає всі очікуючі потоки одночасно, де AutoResetEvent гарантує звільнення лише одного потоку очікування.
Мартін Браун

55

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


36

Взяті з книги Cuts 3.0 про горіхи Джозефа Альбахарі

Тематика в C # - безкоштовна електронна книга

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

Можна було б імітувати цю функціональність за допомогою булевого поля "gateOpen" (оголошеного мінливим ключовим словом) у поєднанні з "spin-sleep" - повторно перевіряючи прапор, а потім спати протягом короткого періоду часу.

Інструкція ManualResetEvents іноді використовується для сигналізації про те, що певна операція завершена, або що ініціалізація потоку завершена і готова виконати роботу.


19

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

AutoResetEvent: припустимо, у вас є три робочі нитки. Якщо будь-який з цих потоків зателефонує 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();

Йдеться більше про стосунки судді / гравців там, незалежно від того, хто з гравців отримав травму, і чекати, коли інші гравці продовжуватимуть працювати. Якщо арбітр каже wait ( 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();
        }
    }
}

13

autoResetEvent.WaitOne()

подібний до

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

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


Це лише концептуально правильно, але не практично. Між WaitOne та Reset може статися контекстний перемикач; це може призвести до тонких помилок.
hofingerandi

2
Чи можете ви зараз проголосувати? Тут ніхто практично не зробить другий блок коду, це питання розуміння різниці.
везенков

11

Добре, як правило, не доречно додавати 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);
        }
    }

}

Вручну скинути вихідний результат

Автоматичний скидання події

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

}

Автоматичне скидання вихідних подій


це був найкращий спосіб зрозуміти все це, скопіював код і запустив усе, змінивши кілька речей, зрозумів це добре зараз
JohnChris

8

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 секунди як аргументи. У циклі "час" він чекає сигналу протягом 2 секунд, а потім продовжує свою роботу. Коли потік отримав сигнал, WaitOne повертає true і виходить з циклу і надрукує "Thread got signal".

Встановити метод

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

autoResetEvent.Set();

ManualResetEvent підтримує булеву змінну в пам'яті. Коли булева змінна помилкова, то вона блокує всі потоки, а коли булева змінна - правда, вона розблокує всі потоки.

Коли ми створюємо экземпляр ManualResetEvent, ми ініціалізуємо його з бульовим значенням за замовчуванням.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

У наведеному вище коді ми ініціалізуємо ManualResetEvent з помилковим значенням, це означає, що всі потоки, які викликають метод WaitOne, блокуються, поки деяка нитка не викличе метод Set ().

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

Метод WaitOne

Цей метод блокує поточну нитку і чекає сигналу іншим потоком. Він повертає true, якщо його приймає сигнал, інше повертає false.

Нижче наведено синтаксис виклику методу WaitOne.

manualResetEvent.WaitOne();

У другому перевантаженні методу WaitOne ми можемо вказати інтервал часу, поки поточний потік чекатиме сигналу. Якщо протягом внутрішнього часу, він не отримує сигнал, він повертає помилку та переходить у наступний рядок методу.

Нижче наведено синтаксис виклику методу WaitOne з часовим інтервалом.

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

Методом WaitOne ми вказали 5 секунд. Якщо об'єкт manualResetEvent не отримує сигнал між 5 секундами, він встановлює змінну isSignalled у значення false.

Встановити метод

Цей метод використовується для передачі сигналу всім потокам очікування. Встановіть () Метод встановіть булеву змінну об'єкта ManualResetEvent в значення true. Усі потоки очікування розблокуються і продовжують далі.

Нижче наведено синтаксис виклику методу Set ().

manualResetEvent.Set();

Спосіб скидання

Після того як ми викликаємо метод Set () на об'єкті ManualResetEvent, його булева інформація залишається істинною. Для скидання значення ми можемо використовувати метод Reset (). Метод скидання змінює булеве значення на хибне.

Нижче наведено синтаксис виклику методу Reset.

manualResetEvent.Reset();

Ми повинні негайно викликати метод скидання після виклику методу Set, якщо ми хочемо кілька разів надсилати сигнал до потоків.


7

Так. Це абсолютно правильно.

Ви можете бачити ManualResetEvent як спосіб вказати стан. Щось увімкнено (Встановити) або вимкнено (Скинути). Поява з деякою тривалістю. Будь-яка нитка, яка чекає настання цього стану, може тривати.

AutoResetEvent порівнянніший із сигналом. Один постріл свідчить про те, що щось сталося. Подія без будь-якої тривалості. Зазвичай, але не обов’язково, що-небудь, що трапилося, є невеликим, і його потрібно обробляти однією ниткою - отже, автоматичне скидання після того, як одна потік поглинула подію.


7

Так, правильно.

Ви можете отримати уявлення, скориставшись цими двома.

Якщо вам потрібно сказати, що ви закінчили якусь роботу та інші (потоки), які чекають цього, тепер можна продовжуватись, вам слід скористатися ManualResetEvent.

Якщо вам потрібно мати взаємний ексклюзивний доступ до будь-якого ресурсу, вам слід скористатися функцією AutoResetEvent.


1

Якщо ви хочете зрозуміти AutoResetEvent і ManualResetEvent, вам потрібно зрозуміти, що не вводити нитки, а переривати їх!

.NET хоче створити програмування низького рівня найбільш віддаленим.

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

Перше, що потрібно зробити, коли трапиться перерва, - це скинути його стан, оскільки апаратне забезпечення працює таким чином:

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

Це різниця між ManualResetEvent і AutoResetEvent.
Якщо ManualResetEvent трапиться, і я не скидаю його, наступного разу я не зможу його слухати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.