我已经阅读了关于此的文档,我想我明白了。 AutoResetEvent 重置时代码通过 event.WaitOne(),但 ManualResetEvent 没有。

这是对的吗?

有帮助吗?

解决方案

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

其他提示

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

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

  

取自C#3.0 Nutshell书,由   Joseph Albahari

使用C#进行线程化 - 免费电子书

ManualResetEvent是AutoResetEvent的变体。它的不同之处在于它在WaitOne调用中通过线程后不会自动复位,因此像gate:call Set这样的函数会打开门,允许任何数量的线程在门口通过WaitOne;调用Reset会关闭门,从而导致服务器队列累积,直到下一次打开。

可以使用布尔值“gateOpen”来模拟此功能。字段(使用volatile关键字声明)与“spin-sleeping”结合使用–反复检查旗帜,然后在短时间内睡觉。

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

我创建了一些简单的例子来阐明对 ManualResetEvent vs 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();
更多关于裁判/球员的关系,无论任何一名球员受伤,等待比赛的人都会继续工作。如果裁判说等待(重置()),则所有玩家将等到下一个信号。

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

作为原子操作

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

}

是。这绝对是正确的。

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

AutoResetEvent与信号更具可比性。一次性表明事情已经发生。没有任何持续时间的事件。通常但不一定是“某事物”。发生的事情很小,需要由一个线程处理 - 因此在单个线程消耗了事件后自动重置。

是的,没错。

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

如果你需要告诉你已经完成了一些工作而其他(线程)正在等待,现在可以继续,你应该使用ManualResetEvent。

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

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

当我们实例化一个AutoResetEvent对象时,我们在构造函数中传递boolean value的默认值。下面是实例化AutoResetEvent对象的语法。

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

WaitOne方法

此方法阻止当前线程并等待其他线程的信号。 WaitOne方法将当前线程置于Sleep线程状态。如果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对象的boolean变量设置为true。所有等待的线程都被解锁并继续进行。

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

manualResetEvent.Set();

重置方法

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

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

manualResetEvent.Reset();

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

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

.NET希望尽可能让人想起低级编程。

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

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

  1. 引脚连接到信号,硬件监听它的变化(信号只能有两种状态)。
  2. 如果信号发生变化意味着发生了某些事情并且硬件将内存变量置于发生状态(即使信号再次发生变化,它仍然如此)。
  3. 程序注意到变量状态并将执行移动到处理函数。
  4. 这里要做的第一件事就是能够再次监听这个中断,将重置这个内存变量转换为未发生的状态。
  5. 这是ManualResetEvent和AutoResetEvent之间的区别。
    如果发生了ManualResetEvent并且我没有重置它,下次它发生时我将无法收听它。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top