在.NET中,ManualResetEvent和AutoResetEvent有什么区别?

580

我已经阅读了这方面的文档,我想我理解了。一个AutoResetEvent在代码通过event.WaitOne() 时会被重置,但是 ManualResetEvent则不会。

这个说法正确吗?


3
希望这个视频可以帮助您理解区别。链接为 https://www.youtube.com/watch?v=xaaRBh07N34 - Vinod Srivastav
11个回答

986

是的。这就像是收费站和门的区别。手动重置事件 ManualResetEvent 就像一扇门,需要手动关闭(重置)。而自动重置事件 AutoResetEvent 则像一个收费站,允许一辆车通过后自动关闭,等待下一辆车到来。


更糟糕的是,不要在设置ARE到WaitOne之间等待太久,否则它会在此期间被重置。我曾经有很多被遗弃的线程就是这样。 - Oliver Friedrich
32
就像一扇门和旋转门一样。 - Constantin
10
哦,所以它们被命名为这个名字。 - Arlen Beiler
1
@DanGoldstein 好的,既然这个问题还没有关闭,如果其他人需要的话,可以参考以下链接:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent(v=vs.110).aspx?f=255&MSPPError=-2147217396 - Phil N DeBlanc
你刚才解释了“自动”和“手动”这两个词的区别。 - Марк Павлович
ManualResetEvent就像一扇门,所有处于WaitOne状态的线程都会在Set被调用后释放,直到下一次Reset被执行。 - abramhum

138

想象一下,AutoResetEventWaitOne()Reset() 视为单个原子操作。

AutoResetEvent 还保证只释放一个等待的线程。


26
如果你在手动重置事件(ManualResetEvent)上同时执行WaitOne和Reset操作,那么它与自动重置事件(AutoResetEvent)仍然会有不同的行为。手动重置事件会同时释放所有等待中的线程,而自动重置事件保证只释放一个正在等待的线程。请注意,这两种操作都是原子操作。 - Martin Brown

62

简短回答是可以。最重要的区别在于,AutoResetEvent只允许一个等待线程继续执行,而ManualResetEvent则会继续允许多个线程同时执行,直到被告知停止(Reset)。


36
来自Joseph Albahari的C# 3.0 Nutshell书籍 Threading in C# - 免费电子书 ManualResetEvent是AutoResetEvent的一种变体。它不同之处在于,在WaitOne调用后,它不会自动重置线程,因此像一个门一样工作:调用Set打开门,允许任意数量的线程通过等待门口;调用Reset关闭门,导致可能会积累一系列的等待者,直到下一次打开。
可以使用布尔型“gateOpen”字段(使用volatile关键字声明)与“自旋休眠”结合来模拟此功能-重复检查标志,然后睡眠一段时间。
ManualResetEvents有时用于表示特定操作已完成,或者线程已完成初始化并准备好执行工作。

21

我创建了简单示例来澄清对ManualResetEventAutoResetEvent的理解。

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

16

autoResetEvent.WaitOne()

类似于

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

作为原子操作


这在概念上是正确的,但在实际操作中并不可行。在 WaitOne 和 Reset 之间可能会发生上下文切换,这可能会导致微妙的错误。 - hofingerandi
2
你现在可以给它点赞吗?实际上,几乎没有人会执行第二个代码块,这是一个理解差异的问题。 - vezenkov

13

通常情况下,将两个答案添加到同一线程不是一个好的做法,但我不想编辑/删除之前的答案,因为它可以在其他方面上有所帮助。

现在,我创建了一个更加综合和易于理解的运行学习控制台应用程序片段。只需在两个不同的控制台上运行示例,并观察行为。在那里,您将更清楚地了解发生了什么。

手动重置事件

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

}

Auto Reset Event Output


这是理解它的最好方法,复制了代码并运行了所有内容,同时更改了一些东西,现在理解得很好。 - JohnChris

9

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

我们通过传递2秒作为参数调用了WaitOne方法。在while循环中,它等待2秒钟的信号,然后继续工作。当线程收到信号时,WaitOne返回true并退出循环,并打印“Thread got signal”。 Set方法 AutoResetEvent Set方法向等待的线程发送信号以继续其工作。以下是调用Set方法的语法。
autoResetEvent.Set();

ManualResetEvent 会在内存中维护一个布尔变量。当这个布尔变量为 false 时,它会阻塞所有线程;当这个布尔变量为 true 时,它会解除所有线程的阻塞。

当我们实例化一个 ManualResetEvent 时,我们会使用默认的布尔值进行初始化。

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

在上面的代码中,我们使用false值初始化ManualResetEvent,这意味着调用WaitOne方法的所有线程都会被阻塞,直到某个线程调用Set()方法。
如果我们使用true值初始化ManualResetEvent,则调用WaitOne方法的所有线程都不会被阻塞,并且可以自由地继续进行。
WaitOne方法
此方法阻塞当前线程并等待其他线程发出的信号。如果接收到信号,则返回true,否则返回false。
以下是调用WaitOne方法的语法。
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();

我们必须在调用Set方法后立即调用Reset方法,如果我们想要多次发送信号给线程。

1
老兄,auto和manual有什么区别? - user1034912
@user1034912 简单来说,假设我们有一所大学,大学有一个主门和保安。自动重置事件(Security)将为每个学生(每个WaitOne())检查他的身份证(Set())并让他进入[每个WaitOne()都需要一个Set()],但手动重置事件只需检查第一个学生的身份证,然后打开主门,其他人就可以进去了。 - Mr.Curious

7

是的,这是正确的。

通过使用这两者之一,您可以得到一个想法。

如果您需要告诉其他线程正在等待某个工作完成并且现在可以继续执行,您应该使用ManualResetEvent。

如果您需要对任何资源进行互斥访问,您应该使用AutoResetEvent。


7

是的,这是完全正确的。

你可以把ManualResetEvent看作是一种表明状态的方式。某些事情处于开启(Set)或关闭(Reset)状态,一个有一定持续时间的事件。任何等待该状态发生的线程都可以继续进行。

AutoResetEvent更像是一个信号。一个单次指示器,表示某件事情已经发生了。通常但不一定是小规模的“某事”需要由单个线程处理-因此在单个线程消耗事件后自动重置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接