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


Answers:


919

是。就像收费站和门之间的区别。的ManualResetEvent是门,其需要手动关闭(复位)。这AutoResetEvent是收费站,可让一辆车通过并自动关闭,直到下一辆车通过。


166
这是一个很好的类比。
twk

更糟糕的是,不要将ARE设置为WaitOne太长时间,否则它将被重置。有很多被遗弃的线程。
奥利弗弗里德里希

24
或像门和旋转门。
康斯坦丁

9
哦,这就是为什么他们被命名为他们的名字。
阿伦·贝勒

1
:@DanGoldstein好,因为这是不关闭,以防其他人想要它 msdn.microsoft.com/en-us/library/...
菲尔ñDeBlanc

124

试想一下,AutoResetEvent执行WaitOne()Reset()作为单个原子操作执行。


16
除了如果您对ManualResetEvent事件执行WaitOne和Reset作为单个原子操作之外,它仍然会执行与AutoResetEvent不同的操作。ManualResetEvent同时释放所有等待的线程,而AutoResetEvent保证只释放一个等待的线程。
马丁·布朗

55

简短的答案是肯定的。最重要的区别是,AutoResetEvent将仅允许一个等待线程继续。另一方面,ManualResetEvent将继续允许线程(甚至同时包含多个线程)继续运行,直到您告诉它停止(重置)为止。


36

摘自约瑟夫·阿尔巴哈里(Joseph Albahari)的C#3.0果壳书

C#中的线程-免费电子书

ManualResetEvent是AutoResetEvent的变体。它的不同之处在于,它在等待WaitOne调用通过某个线程后不会自动重置,因此其功能类似于Gate:调用Set可以打开Gate,从而允许WaitOne处的任何数量的线程通过Gate;调用Reset会关闭门,有可能导致服务员队列累积直到下一次打开。

可以通过将布尔值“ gateOpen”字段(使用volatile关键字声明)与“ spin-sleeping”结合使用来模拟此功能-反复检查该标志,然后休眠一小段时间。

ManualResetEvents有时用于表示特定操作已完成,或者线程已完成初始化并准备执行工作。


19

我创建了一些简单的示例来阐明对ManualResetEventvs的理解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();

无论那里有任何球员受伤,等待其他球员继续工作,无论裁判员/球员之间的关系如何,这都更多。如果裁判说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();
        }
    }
}

13

autoResetEvent.WaitOne()

类似于

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

作为原子操作


这仅在概念上是正确的,但在实践中不是。在WaitOne和Reset之间可能会发生上下文切换。这可能导致细微的错误。
hofingerandi 2015年

2
您现在可以投票吗?在这里几乎没有人会做第二个代码块,这是了解差异的问题。
vezenkov

11

好的,通常在同一线程中添加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);
        }
    }

}

自动重置事件输出


这是理解所有内容,复制代码并运行所有内容的最佳方式,同时更改了几件事,现在就很好理解了
JohnChris

8

AutoResetEvent在内存中维护一个布尔变量。如果布尔变量为false,则它将阻塞线程;如果布尔变量为true,则将取消阻塞线程。

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

我们通过传递2秒作为参数来调用WaitOne方法。在while循环中,它等待信号2秒钟,然后继续工作。当线程收到信号时,WaitOne返回true并退出循环并打印“ Thread got signal”。

设定方法

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()方法将ManualResetEvent对象的布尔变量设置为true。所有等待的线程均被解除阻塞并继续进行。

下面是调用Set()方法的语法。

manualResetEvent.Set();

重置方式

一旦我们在ManualResetEvent对象上调用Set()方法,其布尔值将保持为true。要重置该值,我们可以使用Reset()方法。重置方法将布尔值更改为false。

下面是调用Reset方法的语法。

manualResetEvent.Reset();

如果要多次向线程发送信号,则必须在调用Set方法之后立即调用Reset方法。


7

是。这是绝对正确的。

您可能会看到ManualResetEvent作为指示状态的一种方式。某些东西打开(设置)或关闭(重置)。具有一定持续时间的事件。等待该状态发生的任何线程都可以继续。

AutoResetEvent与信号更具可比性。一枪表明发生了什么事。没有持续时间的事件。通常但并非必须如此,已发生的“事情”很小,需要由单个线程处理-因此,在单个线程消耗完事件后,自动重置。


7

恩,那就对了。

您可以通过使用这两者来获得一个想法。

如果您需要完成某些工作并且需要等待其他(线程)现在可以继续进行,则应该使用ManualResetEvent。

如果需要互斥访问任何资源,则应使用AutoResetEvent。


1

如果您想了解AutoResetEvent和ManualResetEvent,则需要了解不是线程而是中断!

.NET希望尽可能远地构想底层编程。

中断是在低级编程中使用的东西,它等于从低到高的信号(反之亦然)。发生这种情况时,程序将中断其正常执行,并将执行指针移至处理此事件的函数

发生中断时,要做的第一件事是重置其状态,因为硬件以这种方式工作:

  1. 引脚连接到信号,并且硬件会监听它的变化(信号可能只有两种状态)。
  2. 如果信号变化意味着发生了某些事情,并且硬件将存储变量设置为发生的状态(即使信号再次发生变化,它也会保持这种状态)。
  3. 程序注意到变量更改状态并将执行移至处理函数。
  4. 为了能够再次侦听此中断,这里要做的第一件事就是将该内存变量重置为未发生的状态。

这是ManualResetEvent和AutoResetEvent之间的区别。
如果发生ManualResetEvent并且我没有重置它,那么下次发生时,我将无法收听。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.