What is the difference between ManualResetEvent and AutoResetEvent in .NET?
我已经阅读了有关这方面的文档,我认为我理解。当代码通过
这是正确的吗?
对。就像收费站和门的区别。
想象一下,
简短的回答是"是"。最重要的区别是autoresetEvent只允许一个等待线程继续。另一方面,ManualResetEvent将继续允许多个线程(甚至多个线程)继续运行,直到您告诉它停止(重置它)。
Taken from C# 3.0 Nutshell book, by
Joseph Albahari
C-免费电子书中的线程
ManualReseteEvent是AutoResetEvent的变体。它的不同之处在于,在线程通过waitone调用后,它不会自动重置,因此功能类似于gate:调用set打开gate,允许任何数量的线程在gate处等待;调用reset关闭gate,可能导致等待者队列累积到下一个打开为止。
可以使用布尔"gatewopen"字段(用volatile关键字声明)和"spin sleeping"来模拟此功能——反复检查标志,然后短时间睡眠。
ManualReseteEvents有时用于表示特定操作已完成,或者线程已完成初始化并准备好执行工作。
我创建了一些简单的例子来阐明对
1 2 3 | autoReset.Set(); Thread.Sleep(1000); autoReset.Set(); |
当您调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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(); } } } |
在这个例子中,您可以清楚地看到,当您第一次点击
1 2 3 4 5 6 | manualReset.Set(); Thread.Sleep(1000); manualReset.Reset(); Console.WriteLine("Press to release all threads."); Console.ReadLine(); manualReset.Set(); |
更重要的是裁判/球员之间的关系,无论球员是否受伤,等待比赛的其他人将继续工作。如果裁判员说等待,那么所有的球员都会等到下一个信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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(); } } } |
类似于
1 2 3 4 5 6 7 8 | try { manualResetEvent.WaitOne(); } finally { manualResetEvent.Reset(); } |
作为原子操作
好吧,通常在同一个线程中添加2个答案不是一个好的实践,但是我不想编辑/删除我以前的答案,因为它可以在另一种方式上提供帮助。
现在,我创建了一个更全面、更容易理解的运行学习控制台应用程序片段。
只需在两个不同的控制台上运行示例,并观察行为。你会更清楚地知道幕后发生了什么。
手动复位事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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); } } } |
自动重置事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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); } } } |
autoreseteevent在内存中维护一个布尔变量。如果布尔变量为false,则会阻塞线程;如果布尔变量为true,则会阻塞线程。
当我们实例化一个autoreseteevent对象时,我们在构造函数中传递布尔值的默认值。下面是实例化autoreseteevent对象的语法。
1 |
WaitOne法
此方法阻塞当前线程并等待其他线程发出的信号。WaitOne方法将当前线程置于睡眠线程状态。WaitOne方法接收到信号时返回true,否则返回false。
1 | autoResetEvent.WaitOne(); |
waitOne方法的第二个重载等待指定的秒数。如果没有任何信号线程,则继续工作。
1 2 3 4 5 6 7 8 9 10 | static void ThreadMethod() { while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2))) { Console.WriteLine("Continue"); Thread.Sleep(TimeSpan.FromSeconds(1)); } Console.WriteLine("Thread got signal"); } |
我们通过将2秒作为参数传递来调用WaitOne方法。在while循环中,它等待信号2秒钟,然后继续工作。当线程得到信号时,waitone返回true并退出循环并打印"线程得到信号"。
集合方法
autoreseteevent set方法将信号发送到等待线程以继续其工作。下面是调用set方法的语法。
1 | autoResetEvent.Set(); |
ManualResetevent在内存中维护一个布尔变量。当布尔变量为false时,它将阻塞所有线程;当布尔变量为true时,它将阻塞所有线程。
当我们实例化ManualReseteEvent时,我们用默认的布尔值初始化它。
1 |
在上面的代码中,我们用假值初始化manualReseteEvent,这意味着调用waitone方法的所有线程都将阻塞,直到某些线程调用set()方法为止。
如果我们用真值初始化manualReseteEvent,那么调用waitone方法的所有线程都不会阻塞,可以继续执行下去。
韦托法
此方法阻塞当前线程并等待其他线程发出的信号。如果接收到信号,则返回"真";否则返回"假"。
下面是调用WaitOne方法的语法。
1 | manualResetEvent.WaitOne(); |
在waitone方法的第二个重载中,我们可以指定到当前线程等待信号的时间间隔。如果在内部时间内,它没有接收到信号,则返回false并进入下一行方法。
下面是使用时间间隔调用WaitOne方法的语法。
1 | bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5)); |
我们在waitone方法中指定了5秒。如果ManualReseteEvent对象在5秒钟内未接收到信号,则将IsSignalled变量设置为false。
集合方法
此方法用于向所有等待线程发送信号。set()方法将ManualReseteEvent对象布尔变量设置为true。所有等待的线程都被解除阻塞并继续执行。
下面是调用set()方法的语法。
1 | manualResetEvent.Set(); |
复位方法
一旦我们在ManualReseteEvent对象上调用set()方法,它的布尔值将保持为真。要重置该值,可以使用reset()方法。重置方法将布尔值更改为false。
下面是调用reset方法的语法。
1 | manualResetEvent.Reset(); |
如果要多次向线程发送信号,必须在调用set方法之后立即调用reset方法。
是的,没错。
你可以通过这两个的用法得到一个主意。
如果您需要告诉您已经完成了一些工作,而其他(线程)正在等待这一过程,那么现在可以继续,您应该使用manuallresetevent。
如果需要对任何资源进行互斥访问,则应使用autoreseteevent。
对。这是绝对正确的。
您可以看到manuallresetevent作为一种指示状态的方法。某物是开(定)或关(定)的。有一定持续时间的事件。任何等待该状态发生的线程都可以继续。
autoresetEvent更类似于一个信号。一次暗示发生了什么事。没有持续时间的事件。通常情况下,发生的"某物"很小,需要由单个线程处理,因此在单个线程消耗事件后自动重置。
如果您想了解autoreseteevent和manuallresetevent,您需要了解的不是线程,而是中断!
.NET想让人联想到最遥远的底层编程。
中断是在低级编程中使用的东西,它等于从低级变为高级(或viceversa)的信号。发生这种情况时,程序中断其正常执行,并将执行指针移动到处理此事件的函数。
当中断发生时,首先要做的是重置其状态,因为硬件以这种方式工作:
这是ManualReseteEvent和AutoResetEvent之间的区别。如果发生ManualResetEvent,而我没有重置它,那么下次它发生时,我将听不到它。