Я прочитав документацію на це і, думаю, я розумію. Повторне AutoResetEvent
скидання, коли код проходить через event.WaitOne()
, але а - ManualResetEvent
ні.
Це правильно?
Я прочитав документацію на це і, думаю, я розумію. Повторне AutoResetEvent
скидання, коли код проходить через event.WaitOne()
, але а - ManualResetEvent
ні.
Це правильно?
Відповіді:
Так. Це як різниця між платною доріжкою та дверима. ManualResetEvent
Є двері, яка повинна бути закрита (скидання) вручну. Це AutoResetEvent
платний будинок, що дозволяє проїхати одному авто і автоматично закритись, перш ніж може пройти наступний.
Тільки уявіть, що AutoResetEvent
виконується WaitOne()
і Reset()
як одна атомна операція.
Коротка відповідь - так. Найголовніша відмінність полягає в тому, що програма AutoResetEvent дозволить продовжувати лише одну єдину нитку очікування. З іншого боку, ManualResetEvent дозволить продовжувати потоки, кілька одночасно навіть, до тих пір, поки ви не скажете зупинитися (Скинути його).
Взяті з книги Cuts 3.0 про горіхи Джозефа Альбахарі
Тематика в C # - безкоштовна електронна книга
ManualResetEvent є варіантом AutoResetEvent. Він відрізняється тим, що він автоматично не скидається після того, як потік пропускається через виклик WaitOne, і так функціонує, як ворота: виклик Set відкриває ворота, дозволяючи будь-яку кількість потоків, які WaitOne проходять через ворота; виклик Скидання закриває ворота, викликаючи, можливо, чергу офіціантів накопичуватися до наступного відкриття.
Можна було б імітувати цю функціональність за допомогою булевого поля "gateOpen" (оголошеного мінливим ключовим словом) у поєднанні з "spin-sleep" - повторно перевіряючи прапор, а потім спати протягом короткого періоду часу.
Інструкція ManualResetEvents іноді використовується для сигналізації про те, що певна операція завершена, або що ініціалізація потоку завершена і готова виконати роботу.
Я створив прості приклади для уточнення розуміння 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();
}
}
}
autoResetEvent.WaitOne()
подібний до
try
{
manualResetEvent.WaitOne();
}
finally
{
manualResetEvent.Reset();
}
як атомна операція
Добре, як правило, не доречно додавати 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);
}
}
}
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, якщо ми хочемо кілька разів надсилати сигнал до потоків.
Так. Це абсолютно правильно.
Ви можете бачити ManualResetEvent як спосіб вказати стан. Щось увімкнено (Встановити) або вимкнено (Скинути). Поява з деякою тривалістю. Будь-яка нитка, яка чекає настання цього стану, може тривати.
AutoResetEvent порівнянніший із сигналом. Один постріл свідчить про те, що щось сталося. Подія без будь-якої тривалості. Зазвичай, але не обов’язково, що-небудь, що трапилося, є невеликим, і його потрібно обробляти однією ниткою - отже, автоматичне скидання після того, як одна потік поглинула подію.
Так, правильно.
Ви можете отримати уявлення, скориставшись цими двома.
Якщо вам потрібно сказати, що ви закінчили якусь роботу та інші (потоки), які чекають цього, тепер можна продовжуватись, вам слід скористатися ManualResetEvent.
Якщо вам потрібно мати взаємний ексклюзивний доступ до будь-якого ресурсу, вам слід скористатися функцією AutoResetEvent.
Якщо ви хочете зрозуміти AutoResetEvent і ManualResetEvent, вам потрібно зрозуміти, що не вводити нитки, а переривати їх!
.NET хоче створити програмування низького рівня найбільш віддаленим.
Переривання - це те, що використовується в програмуванні низького рівня, що дорівнює сигналу, який з низького став високим (або навпаки). Коли це трапляється, програма перериває її нормальне виконання та переміщує покажчик виконання на функцію, яка обробляє цю подію .
Перше, що потрібно зробити, коли трапиться перерва, - це скинути його стан, оскільки апаратне забезпечення працює таким чином:
Це різниця між ManualResetEvent і AutoResetEvent.
Якщо ManualResetEvent трапиться, і я не скидаю його, наступного разу я не зможу його слухати.