ChatGPT解决这个技术问题 Extra ChatGPT

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

我已经阅读了这方面的文档,我想我明白了。当代码通过 event.WaitOne() 时,AutoResetEvent 会重置,但 ManualResetEvent 不会。

这个对吗?

愿这个 av 有助于理解差异youtube.com/watch?v=xaaRBh07N34

Y
Yves M.

是的。这就像收费站和门之间的区别。 ManualResetEvent 是门,需要手动关闭(重置)。 AutoResetEvent 是一个收费站,允许一辆车通过并在下一辆车通过之前自动关闭。


更糟糕的是,不要等太久才将 ARE 设置为 WaitOne,否则它会同时被重置。有很多废弃的线程。
或者像一扇门和一个旋转门。
哦,这就是为什么他们被命名为他们的名字。
@DanGoldstein 很好,因为它没有关闭,以防其他人想要它:msdn.microsoft.com/en-us/library/…
您刚刚解释了“自动”和“手动”这两个词之间的区别。
M
Michael Damatov

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

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


除了如果您在 ManualResetEvent 事件上将 WaitOne 和 Reset 作为单个原子操作执行,它仍然会执行与 AutoResetEvent 不同的操作。 ManualResetEvent 同时释放所有等待线程,而 AutoResetEvent 保证只释放一个等待线程。
M
Martin Brown

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


佚名

摘自 Joseph Albahari 的 C# 3.0 Nutshell 书

Threading in C# - Free E-Book

ManualResetEvent 是 AutoResetEvent 的变体。不同之处在于它不会在 WaitOne 调用允许线程通过后自动重置,因此其功能类似于门:调用 Set 打开门,允许 WaitOne 在门的任意数量的线程通过;调用 Reset 会关闭门,可能会导致等待者排队等待下一次打开。

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

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


i
ivcubr

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

v
vezenkov

autoResetEvent.WaitOne()

类似于

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

作为原子操作


这只是概念上正确,但实际上并不正确。在 WaitOne 和 Reset 之间可能会发生上下文切换;这可能会导致细微的错误。
你现在可以投票了吗?没有人会在这里实际执行第二个代码块,这是理解差异的问题。
T
Teoman shipahi

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

}

https://i.stack.imgur.com/tvKU2.png

自动重置事件

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

}

https://i.stack.imgur.com/hyLp4.png


这是理解这一切的最佳方式,复制代码并运行它,同时更改一些东西,现在很好理解
M
Masoud Siahkali

AutoResetEvent 在内存中维护一个布尔变量。如果布尔变量为假,则阻塞线程,如果布尔变量为真,则解除线程阻塞。

当我们实例化一个 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 并退出循环并打印“线程收到信号”。

设置方法

AutoResetEvent Set 方法将信号发送到等待线程以继续其工作。下面是调用 Set 方法的语法。

autoResetEvent.Set();

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

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

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

在上面的代码中,我们将 ManualResetEvent 初始化为 false 值,这意味着所有调用 WaitOne 方法的线程都会阻塞,直到某个线程调用 Set() 方法。

如果我们将 ManualResetEvent 初始化为 true 值,那么所有调用 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() 方法,它的布尔值就保持为真。要重置值,我们可以使用 Reset() 方法。 Reset 方法将布尔值更改为 false。

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

manualResetEvent.Reset();

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


亲,自动和手动有什么区别?
@user1034912 简而言之,假设我们有一所大学,而大学有大门和保安。自动重置事件(安全)将检查每个学生(每个 WaitOne())的身份证(Set())并将他留在里面[每个 WaitOne()需要一个 Set()],但是手动重置事件,一旦他检查了第一个学生为了身份证,他打开大门休息,每个人都可以去
B
Boaz

是的。这是绝对正确的。

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

AutoResetEvent 更类似于信号。一枪表明发生了什么事。没有任何持续时间的事件。通常但不一定发生的“事情”很小,需要由单个线程处理 - 因此在单个线程消耗事件后自动重置。


S
Swapnil Patil

是的,这是正确的。

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

如果您需要告诉您已完成某些工作并且等待此操作的其他(线程)现在可以继续,您应该使用 ManualResetEvent。

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


p
princio

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

.NET 希望在最遥远的地方实现低级编程。

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

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

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

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