从 .NET Framework 版本2.0 开始, AutoResetEvent 和ManualResetEvent 派生自 EventWaitHandle 类。 在 ManualResetEvent 功能上等效于 EventWaitHandle 使用创建的EventResetMode.ManualReset。使用 EventResetMode.AutoReset 标志创建的 EventWaitHandle 会在释放单个等待线程后自动进行重置。本文主要介绍.NET(C#)中AutoResetEvent 和 ManualResetEvent的使用总结。

1、AutoResetEvent 和 ManualResetEvent

ManualResetEvent表示线程同步事件,收到信号时,必须手动重置该事件。AutoResetEvent表示线程同步事件在一个等待线程释放后收到信号时自动重置。ManualResetEvent 调用一次Set()后将允许恢复所有被阻塞线程。需手动在调用WaitOne()之后调用Reset()重置信号量状态为非终止,然后再次调用WaitOne()时才能继续阻塞线程。AutoResetEvent调用一次Set()只能继续被阻塞的一个线程,多次调用Set()才行,但不需手动调用Reset();再次调用WaitOne()的时候又能阻塞线程。AutoResetEvent不保证每次调用 Set()方法都将释放一个线程。 如果两次调用都过于接近,因此在释放线程之前发生第二次调用,则只释放一个线程。 这就像第二次调用没有发生。 此外,如果在没有 Set()等待的线程并且已 AutoResetEvent 发出信号的情况下调用,则调用不起作用。

2、AutoResetEvent的使用

参考文档https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.autoresetevent?view=net-6.0

AutoResetEvent(false)为创建在无信号状态。WaitOne()方法会阻塞,调用Set()方法变为有信号状态,只能继续被阻塞的一个线程的WaitOne()方法,会自动变为信号状态,不需手动调用Reset()new AutoResetEvent(true)为创建在有信号状态。调用Set()方法变为有信号状态,只能继续被阻塞的一个线程的WaitOne()方法,会自动变为信号状态,不需手动调用Reset()

例如,

using System;
using System.Threading;
namespace ConsoleApplication
{
    class Program
    {
        private static AutoResetEvent event_1 = new AutoResetEvent(true);
        private static AutoResetEvent event_2 = new AutoResetEvent(false);
        static void Main()
        {
            for (int i = 1; i < 4; i++)
            {
                Thread t = new Thread(ThreadProc);
                t.Name = "Thread_" + i;
                t.Start();
            }
            Thread.Sleep(250);
            for (int i = 0; i < 2; i++)
            {
                Console.WriteLine("按Enter键释放另一个线程。");
                Console.ReadLine();
                event_1.Set();
                Thread.Sleep(250);
            }
            Console.WriteLine("\r\nevent_2所有线程现在都在等待");
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine("按Enter键释放线程。");
                Console.ReadLine();
                event_2.Set();
                Thread.Sleep(250);
            }
        }
        static void ThreadProc()
        {
            string name = Thread.CurrentThread.Name;
            Console.WriteLine("{0} 等待event_1", name);
            event_1.WaitOne();
            Console.WriteLine("{0} 从event_1释放", name);
            Console.WriteLine("{0} 等待event_2", name);
            event_2.WaitOne();
            Console.WriteLine("{0} 从event_2释放", name);
            Console.WriteLine("{0} 结束", name);
        }
    }
}

3、ManualResetEvent的使用

参考文档https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.manualresetevent?view=net-6.0

new ManualResetEvent(false)为创建在无信号状态。WaitOne()方法会阻塞,调用Set()方法变为有信号状态,WaitOne()方法不会阻塞,需要调用Reset()方法变为无信号状态,WaitOne()方法将会阻塞。new ManualResetEvent(true)为创建在有信号状态。Wait()方法不会阻塞,需要调用Reset()方法变为无信号状态,Wait()方法将会阻塞。

例如,

using System;
using System.Threading;
namespace ConsoleApplication
{
    class Program
    {
        //// mre用于手动阻塞和释放线程。false是创建在无信号状态。
        private static ManualResetEvent mre = new ManualResetEvent(false);
        static void Main()
        {
            Console.WriteLine("启动 3个在ManualResetEvent上阻塞的命名线程");
            for (int i = 0; i <= 2; i++)
            {
                Thread t = new Thread(ThreadProc);
                t.Name = "Thread_" + i;
                t.Start();
            }
            Thread.Sleep(500);
            Console.WriteLine("按回车键,当三个线程都启动后,调用Set()释放所有线程。");
            Console.ReadLine();
            mre.Set();
            Thread.Sleep(500);
            Console.WriteLine("按回车键,当ManualResetEvent有信号时,调用WaitOne()的线程不会阻塞。");
            Console.ReadLine();
            for (int i = 3; i <= 4; i++)
            {
                Thread t = new Thread(ThreadProc);
                t.Name = "Thread_" + i;
                t.Start();
            }
            Thread.Sleep(500);
            Console.WriteLine("按回车键,调用Reset(),这样线程在调用WaitOne()时就会再次阻塞。");
            Console.ReadLine();
            mre.Reset();
            // Start a thread that waits on the ManualResetEvent.
            Thread t5 = new Thread(ThreadProc);
            t5.Name = "Thread_5";
            t5.Start();
            Thread.Sleep(500);
            Console.WriteLine("按回车键,调用Set()并结束运行。");
            Console.ReadLine();
            mre.Set();
            // 如在Visual Studio中运行这个例子,取消下面这行注释:
            //Console.ReadLine();
        }
        private static void ThreadProc()
        {
            string name = Thread.CurrentThread.Name;
            Console.WriteLine(name + " 调用 mre.WaitOne()");
            mre.WaitOne();
            Console.WriteLine(name + " 结束");
        }
    }
}

推荐文档