我已经阅读了这方面的文档,我想我理解了。一个AutoResetEvent
在代码通过event.WaitOne()
时会被重置,但是 ManualResetEvent
则不会。
这个说法正确吗?
我已经阅读了这方面的文档,我想我理解了。一个AutoResetEvent
在代码通过event.WaitOne()
时会被重置,但是 ManualResetEvent
则不会。
这个说法正确吗?
是的。这就像是收费站和门的区别。手动重置事件 ManualResetEvent
就像一扇门,需要手动关闭(重置)。而自动重置事件 AutoResetEvent
则像一个收费站,允许一辆车通过后自动关闭,等待下一辆车到来。
想象一下,AutoResetEvent
将 WaitOne()
和 Reset()
视为单个原子操作。
AutoResetEvent
还保证只释放一个等待的线程。
简短回答是可以。最重要的区别在于,AutoResetEvent只允许一个等待线程继续执行,而ManualResetEvent则会继续允许多个线程同时执行,直到被告知停止(Reset)。
我创建了简单示例来澄清对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();
}
作为原子操作
通常情况下,将两个答案添加到同一线程不是一个好的做法,但我不想编辑/删除之前的答案,因为它可以在其他方面上有所帮助。
现在,我创建了一个更加综合和易于理解的运行学习控制台应用程序片段。只需在两个不同的控制台上运行示例,并观察行为。在那里,您将更清楚地了解发生了什么。
手动重置事件
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(自动重置事件)在内存中维护一个布尔变量。如果布尔变量为false,则会阻塞线程;如果布尔变量为true,则会解除线程的阻塞。
当我们实例化一个AutoResetEvent对象时,我们在构造函数中传递布尔值的默认值。以下是实例化AutoResetEvent对象的语法。
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
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");
}
autoResetEvent.Set();
ManualResetEvent 会在内存中维护一个布尔变量。当这个布尔变量为 false 时,它会阻塞所有线程;当这个布尔变量为 true 时,它会解除所有线程的阻塞。
当我们实例化一个 ManualResetEvent 时,我们会使用默认的布尔值进行初始化。
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
manualResetEvent.WaitOne();
在 WaitOne 方法的第二个重载中,我们可以指定当前线程等待信号的时间间隔。如果在时间间隔内未收到信号,则返回 false 并进入方法的下一行。
以下是使用时间间隔调用 WaitOne 方法的语法。
bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
我们在WaitOne方法中指定了5秒钟。如果manualResetEvent对象在5秒钟内没有接收到信号,则将isSignalled变量设置为false。
Set方法
此方法用于向所有等待线程发送信号。 Set()方法将ManualResetEvent对象的布尔变量设置为true。 所有等待的线程都将解除阻塞并继续执行。
下面是调用Set()方法的语法。
manualResetEvent.Set();
重置方法
一旦我们在 ManualResetEvent 对象上调用 Set() 方法,它的布尔值将保持为 true。要重置该值,我们可以使用 Reset() 方法。Reset 方法将布尔值更改为 false。
以下是调用 Reset 方法的语法。
manualResetEvent.Reset();
是的,这是正确的。
通过使用这两者之一,您可以得到一个想法。
如果您需要告诉其他线程正在等待某个工作完成并且现在可以继续执行,您应该使用ManualResetEvent。
如果您需要对任何资源进行互斥访问,您应该使用AutoResetEvent。
是的,这是完全正确的。
你可以把ManualResetEvent看作是一种表明状态的方式。某些事情处于开启(Set)或关闭(Reset)状态,一个有一定持续时间的事件。任何等待该状态发生的线程都可以继续进行。
AutoResetEvent更像是一个信号。一个单次指示器,表示某件事情已经发生了。通常但不一定是小规模的“某事”需要由单个线程处理-因此在单个线程消耗事件后自动重置。