ฉันได้อ่านเอกสารเกี่ยวกับเรื่องนี้แล้วและฉันคิดว่าฉันเข้าใจ AutoResetEvent
รีเซ็ตเมื่อรหัสผ่านevent.WaitOne()
แต่ManualResetEvent
ไม่ได้
ถูกต้องหรือไม่
ฉันได้อ่านเอกสารเกี่ยวกับเรื่องนี้แล้วและฉันคิดว่าฉันเข้าใจ AutoResetEvent
รีเซ็ตเมื่อรหัสผ่านevent.WaitOne()
แต่ManualResetEvent
ไม่ได้
ถูกต้องหรือไม่
คำตอบ:
ใช่. มันเหมือนความแตกต่างระหว่างโทลเวย์กับประตู ManualResetEvent
เป็นประตูซึ่งจะต้องมีการปิด (ตั้งค่า) ด้วยตนเอง AutoResetEvent
เป็นด่านที่ช่วยให้รถคันหนึ่งไปโดยอัตโนมัติและปิดก่อนที่หนึ่งต่อไปจะได้รับผ่าน
แค่คิดว่ามันAutoResetEvent
ดำเนินการWaitOne()
และReset()
เป็นปฏิบัติการอะตอมเดียว
คำตอบสั้น ๆ คือใช่ ความแตกต่างที่สำคัญที่สุดคือ AutoResetEvent จะอนุญาตให้เธรดที่รอเพียงหนึ่งเธรดดำเนินการต่อ ManualResetEvent ในอีกทางหนึ่งจะอนุญาตให้เธรดหลายตัวพร้อมกันเพื่อดำเนินการต่อจนกว่าคุณจะบอกให้หยุด (รีเซ็ต)
นำมาจากหนังสือสรุป C # 3.0 โดย Joseph Albahari
ManualResetEvent เป็นรูปแบบของ AutoResetEvent มันแตกต่างกันโดยที่มันจะไม่รีเซ็ตโดยอัตโนมัติหลังจากที่เธรดปล่อยให้ผ่านการเรียก WaitOne และฟังก์ชั่นเช่น gate: การเรียก Set Set จะเปิด gate ซึ่งอนุญาตให้มีเธรดจำนวนใด ๆ ที่ WaitOne ที่เกตผ่าน; การรีเซ็ตการเรียกปิดประตูทำให้คิวของบริกรที่จะสะสมจนกว่าจะเปิดต่อไป
เราสามารถจำลองการทำงานนี้ด้วยฟิลด์ "gateOpen" บูลีน (ประกาศด้วยคำสำคัญระเหย) ร่วมกับ "สปิน - หลับ" - ตรวจสอบสถานะซ้ำแล้วซ้ำอีกจากนั้นก็หลับเป็นระยะเวลาสั้น ๆ
ManualResetEvents บางครั้งใช้เพื่อส่งสัญญาณว่าการดำเนินการเฉพาะเสร็จสมบูรณ์หรือว่าการเริ่มต้นของเธรดเสร็จสมบูรณ์และพร้อมที่จะทำงาน
ฉันสร้างตัวอย่างง่ายๆเพื่อชี้แจงทำความเข้าใจของVSManualResetEvent
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 คำตอบในเธรดเดียวกัน แต่ฉันไม่ต้องการแก้ไข / ลบคำตอบก่อนหน้าของฉันเพราะมันสามารถช่วยในลักษณะอื่น
ตอนนี้ฉันได้สร้างตัวอย่างข้อมูลแอพคอนโซลที่เข้าใจได้ง่ายขึ้นและเข้าใจง่ายด้านล่าง
เพียงแค่เรียกใช้ตัวอย่างบนสองคอนโซลที่แตกต่างกันและสังเกตพฤติกรรม คุณจะได้รับความคิดที่ชัดเจนมากขึ้นนั่นคือสิ่งที่เกิดขึ้นเบื้องหลัง
เหตุการณ์รีเซ็ตด้วยตนเอง
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 จะส่งกลับค่าจริงหากได้รับสัญญาณอื่นจะคืนค่าเป็นเท็จ
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 จะส่งกลับค่าจริงและออกจากลูปแล้วพิมพ์ "Thread got signal"
กำหนดวิธีการ
วิธีการตั้งค่า AutoResetEvent ส่งสัญญาณไปยังเธรดที่รอเพื่อดำเนินการต่อ ด้านล่างเป็นไวยากรณ์ของการเรียกวิธีการตั้งค่า
autoResetEvent.Set();
ManualResetEventรักษาตัวแปรบูลีนในหน่วยความจำ เมื่อตัวแปรบูลีนเป็นเท็จมันจะบล็อกเธรดทั้งหมดและเมื่อตัวแปรบูลีนเป็นจริงมันจะปลดบล็อกเธรดทั้งหมด
เมื่อเรายกตัวอย่าง ManualResetEvent เราจะเริ่มต้นมันด้วยค่าบูลีนเริ่มต้น
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ในรหัสข้างต้นเราเริ่มต้น ManualResetEvent ด้วยค่าเท็จนั่นหมายถึงกระทู้ทั้งหมดที่เรียกวิธี WaitOne จะปิดกั้นจนกว่าบางกระทู้เรียกวิธีการ Set ()
หากเราเริ่มต้น ManualResetEvent ด้วยค่าจริงเธรดทั้งหมดที่เรียกใช้เมธอด WaitOne จะไม่บล็อกและฟรีเพื่อดำเนินการต่อไป
วิธี WaitOne
วิธีการนี้จะบล็อกเธรดปัจจุบันและรอสัญญาณจากเธรดอื่น มันจะกลับมาจริงถ้ามันได้รับสัญญาณอื่นกลับเท็จ
ด้านล่างเป็นไวยากรณ์ของการเรียกวิธี WaitOne
manualResetEvent.WaitOne();
ในโอเวอร์โหลดที่สองของเมธอด WaitOne เราสามารถระบุช่วงเวลาจนกระทั่งเธรดปัจจุบันรอสัญญาณ หากภายในระยะเวลาภายในจะไม่ได้รับสัญญาณที่ส่งกลับเท็จและเข้าสู่วิธีการบรรทัดถัดไป
ด้านล่างนี้เป็นรูปแบบของการเรียกวิธี WaitOne พร้อมช่วงเวลา
bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
เราได้ระบุ 5 วินาทีในวิธี WaitOne หากอ็อบเจ็กต์ manualResetEvent ไม่ได้รับสัญญาณระหว่าง 5 วินาทีมันจะตั้งค่าตัวแปร isSignalled เป็น false
กำหนดวิธีการ
วิธีนี้ใช้สำหรับส่งสัญญาณไปยังเธรดที่รออยู่ทั้งหมด Set () วิธีการตั้งค่าตัวแปรบูลีน ManualResetEvent วัตถุเป็นจริง เธรดที่รอทั้งหมดจะถูกปลดบล็อกและดำเนินการต่อไป
ด้านล่างเป็นไวยากรณ์ของการโทร Set () วิธีการ
manualResetEvent.Set();
รีเซ็ตวิธี
เมื่อเราเรียกใช้เมธอด Set () บนวัตถุ ManualResetEvent บูลีนจะยังคงเป็นจริง ในการรีเซ็ตค่าเราสามารถใช้วิธีรีเซ็ต () วิธีการรีเซ็ตเปลี่ยนค่าบูลีนเป็นเท็จ
ด้านล่างเป็นไวยากรณ์ของการเรียกใช้วิธีการตั้งค่าใหม่
manualResetEvent.Reset();
เราจะต้องเรียกใช้วิธีการรีเซ็ตทันทีหลังจากเรียกวิธีการตั้งค่าถ้าเราต้องการส่งสัญญาณไปยังกระทู้หลายครั้ง
ใช่. ถูกต้องอย่างแน่นอน
คุณสามารถดู ManualResetEvent เป็นวิธีการระบุสถานะ บางสิ่งบางอย่างเปิดอยู่ (ตั้งค่า) หรือปิด (รีเซ็ต) เหตุการณ์ที่เกิดขึ้นในระยะเวลาหนึ่ง เธรดใด ๆ ที่รอสถานะนั้นจะเกิดขึ้นได้
AutoResetEvent เปรียบได้กับสัญญาณมากกว่า การยิงครั้งเดียวบ่งชี้ว่ามีบางอย่างเกิดขึ้น เหตุการณ์ที่เกิดขึ้นโดยไม่มีระยะเวลาใด ๆ โดยทั่วไปแล้ว แต่ไม่จำเป็นว่า "บางอย่าง" ที่เกิดขึ้นมีขนาดเล็กและจำเป็นต้องจัดการโดยเธรดเดี่ยว - ดังนั้นการรีเซ็ตอัตโนมัติหลังจากเธรดเดี่ยวใช้เหตุการณ์
ใช่ถูกแล้ว.
คุณสามารถรับแนวคิดจากการใช้สองสิ่งนี้
หากคุณจำเป็นต้องบอกว่าคุณทำงานเสร็จแล้วและงานอื่น ๆ (เธรด) ที่รอให้ขั้นตอนนี้สามารถดำเนินการต่อได้คุณควรใช้ ManualResetEvent
หากคุณต้องการเข้าถึงทรัพยากรใด ๆ ร่วมกันคุณควรใช้ AutoResetEvent
หากคุณต้องการที่จะเข้าใจ AutoResetEvent และ ManualResetEvent คุณต้องเข้าใจไม่เกลียว แต่ขัดจังหวะ!
.NET ต้องการคิดในการเขียนโปรแกรมระดับต่ำสุดไกลที่สุด
การขัดจังหวะเป็นสิ่งที่ใช้ในการเขียนโปรแกรมระดับต่ำซึ่งเท่ากับสัญญาณที่จากต่ำกลายเป็นสูง (หรือ viceversa) เมื่อสิ่งนี้เกิดขึ้นโปรแกรมจะขัดจังหวะการทำงานปกติและเลื่อนตัวชี้การเรียกใช้งานไปยังฟังก์ชันที่จัดการกับเหตุการณ์นี้
สิ่งแรกที่ต้องทำเมื่อมีการขัดจังหวะเกิดขึ้นคือการรีเซ็ตสถานะเนื่องจากฮาร์ดแวร์ทำงานในลักษณะนี้:
นี่คือความแตกต่างระหว่าง ManualResetEvent และ AutoResetEvent
หาก ManualResetEvent เกิดขึ้นและฉันไม่รีเซ็ตในครั้งต่อไปที่เกิดขึ้นฉันจะไม่สามารถฟังได้