线程同步 –AutoResetEvent和ManualResetEvent

上一篇介绍了通过lock关键字和Monitor类型进行线程同步,本篇中就介绍一下通过同步句柄进行线程同步。

在Windows系统中,可以使用内核对象进行线程同步,内核对象由系统创建并维护。内核对象为内核所拥有,所以不同进程可以访问同一个内核对象, 如进程、线程、事件、信号量、互斥量等都是内核对象。其中,信号量,互斥体,事件是Windows专门用来进行线程同步的内核对象。

在.NET中,有一个WaitHandle抽象类,这个类型封装了一个Windows内核对象句柄,在C#代码中,我们就可以使用WaitHandle类型(确切的说是子类型)的实例进行线程同步了。下面图中显示了WaitHandle类型的所有子类,下面对各个类型进行介绍。

WaitHandle类型

WaitHandle类型中的SafeWaitHandle属性就是我们前面提到的Windows内核对象句柄,WaitHandle是一个抽象类,不能实例化。

下面看看WaitHandle中常用方法:

  • 实例方法:

    • WaitOne():阻止当前线程,直到当前 WaitHandle 收到信号
    • WaitOne(Int32):阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数表示超时时间
  • 静态方法:
    • WaitAll(WaitHandle[]):等待指定数组中的所有元素都收到信号
    • WaitAll(WaitHandle[], Int32):等待指定数组中的所有元素接收信号,同时使用 32 位带符号整数表示超时时间
    • WaitAny(WaitHandle[]):等待指定数组中的任一元素收到信号
    • WaitAny(WaitHandle[], Int32):等待指定数组中的任意元素接收信号,同时使用 32 位带符号整数表示超时时间

同步事件EventWaitHandle

EventWaitHandle 类允许线程通过发信号互相通信, 通常情况下,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。

在EventWaitHandle类型中,除了父类中的方法,又有自己的特有方法,下面几个是比较常用的:

  • 实例方法:

    • Set:将事件状态设置为终止状态,允许一个或多个等待线程继续
    • Reset:将事件状态设置为非终止状态,导致线程阻止
  • 静态方法:
    • OpenExisting(String):打开指定名称为同步事件(如果已经存在);通过一个命名的EventWaitHandle我们可以进行进程之间的线程同步,未命名的EventWaitHandle只能进行本进程中的线程同步

EventWaitHandle类型有两个子类AutoResetEvent和ManualResetEvent,这两个子类分别代表了EventWaitHandle类型对事件状态的重置模式。在释放单个等待线程后,用 EventResetMode.AutoReset 标志创建的 EventWaitHandle 在终止时会自动重置; 用 EventResetMode.ManualReset 标志创建的 EventWaitHandle 一直保持终止状态,直到它的 Reset 方法被调用

通过EventWaitHandle类型的构造函数,我们可以通过参数指定EventResetMode,从而选择事件状态的重置模式。当然,我们也可以直接使用EventWaitHandle的两个子类。

public EventWaitHandle(bool initialState, EventResetMode mode);

public enum EventResetMode
{
    AutoReset = 0,
    ManualReset = 1,
}

使用AutoResetEvent

AutoResetEvent 允许线程通过发信号互相通信:

  • 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
  • 线程通过调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。

可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态:如果初始状态为终止状态,则为 true;否则为 false。

下面看一个例子:

namespace AutoResetEventTest
{
    class Program
    {
        //AutoResetEvent实例初始为非终止状态
        private static AutoResetEvent autoResetEvent = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            new Thread(() =>
            {
                while (true)
                {
                    //调用WaitOne来等待信号,并设置超时时间为5秒
                    bool status = autoResetEvent.WaitOne(5000);
                    if (status)
                    {
                        Console.WriteLine("ThreadOne get the signal");
                    }
                    else
                    {
                        Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
                        break;
                    }
                }
                Console.WriteLine("ThreadOne Exit");

            }).Start();

            new Thread(() =>
            {
                while (true)
                {
                    //调用WaitOne来等待信号,并设置超时时间为5秒
                    bool status = autoResetEvent.WaitOne(5000);
                    if (status)
                    {
                        Console.WriteLine("ThreadTwo get the signal");
                    }
                    else
                    {
                        Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
                        break;
                    }
                }
                Console.WriteLine("ThreadTwo Exit");

            }).Start();

            Random ran = new Random();
            for (int i = 0; i < 8; i++)
            {

                Thread.Sleep(ran.Next(500, 1000));
                //通过Set向 AutoResetEvent 发信号以释放等待线程
                Console.WriteLine("Main thread send the signal");
                autoResetEvent.Set();
            }

            Console.Read();
        }
    }
}

代码的输出为下,通过结果也可以验证,每次调用Set方法之后,AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。

使用ManualResetEvent

像AutoResetEvent一样,ManualResetEvent 也是线程通过发信号互相通信:

  • 线程通过调用 ManualResetEvent上的 WaitOne 来等待信号。 如果 ManualResetEvent为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
  • 线程通过调用 Set 向 ManualResetEvent发信号以释放等待线程。 与AutoResetEvent不同的是,ManualResetEvent将一直保持终止状态,并释放所有等待线程,直到有线程通过Reset将 ManualResetEvent 置于非终止状态。
  • 线程通过调用 Reset以将 ManualResetEvent 置于非终止状态。

可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。

看一个例子:

namespace ManualResetEventTest
{
    class Program
    {
        //ManualResetEvent实例初始为非终止状态
        private static ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            new Thread(() =>
            {
                //调用WaitOne来等待信号
                manualResetEvent.WaitOne();
                Console.WriteLine("Thread get the signal - the first time");

                Thread.Sleep(1000);
                manualResetEvent.WaitOne();
                Console.WriteLine("Thread get the signal - the second time");

                //调用Reset来以将 ManualResetEvent 置于非终止状态
                Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
                manualResetEvent.Reset();

                manualResetEvent.WaitOne();
                Console.WriteLine("Thread get the signal - the third time");

                Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
                manualResetEvent.Reset();

                //调用WaitOne来等待信号,并设置超时时间为3秒
                manualResetEvent.WaitOne(3000);
                Console.WriteLine("timeout while waiting for signal");
            }).Start();

            //通过Set向 ManualResetEvent 发信号以释放等待线程
            Console.WriteLine("Main thread set ManualResetEvent to signaled");
            manualResetEvent.Set();
            Thread.Sleep(3000);
            Console.WriteLine("Main thread set ManualResetEvent to signaled");
            manualResetEvent.Set();

            Console.Read();
        }
    }
}

代码的输出如下,通过结果验证了,每次调用Set都会将ManualResetEvent设置为终止状态,并释放所有等待线程。只有手动调用 Reset才能将 ManualResetEvent 置于非终止状态。

实现进程间线程同步

前一篇文章中介绍的lock和Monitor只能进行同一个进程中的线程同步。

但是,由于同步事件EventWaitHandle是基于内核事件的,所以说,它可以实现进程之间的线程同步。

基于前面AutoResetEvent的例子稍作修改:

class Program
{

    private static EventWaitHandle eventWaitHandle;

    private static bool newEventWaitHandleObj = true;

    static void Main(string[] args)
    {
        string EventWaitHandleName = "EventWaitHandleTest";
        try
        {
            //尝试打开已有的同步事件
            eventWaitHandle = EventWaitHandle.OpenExisting("EventWaitHandleTest");
            newEventWaitHandleObj = false;
        }
        catch (WaitHandleCannotBeOpenedException e)
        {
            Console.WriteLine("EventWaitHandle named {0} is not exist, error message: {1}", EventWaitHandleName, e.Message);
            //实例化同步事件,初始为非终止状态,设置为自动重置模式
            eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "EventWaitHandleTest");
            Console.WriteLine("Create EventWaitHandle {0}", EventWaitHandleName);
            newEventWaitHandleObj = true;
        }

        new Thread(() =>
        {
            while (true)
            {
                //调用WaitOne来等待信号,并设置超时时间为5秒
                bool status = eventWaitHandle.WaitOne(5000);
                if (status)
                {
                    Console.WriteLine("ThreadOne get the signal");
                }
                else
                {
                    Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
                    break;
                }
            }
            Console.WriteLine("ThreadOne Exit");

        }).Start();

        new Thread(() =>
        {
            while (true)
            {
                //调用WaitOne来等待信号,并设置超时时间为5秒
                bool status = eventWaitHandle.WaitOne(5000);
                if (status)
                {
                    Console.WriteLine("ThreadTwo get the signal");
                }
                else
                {
                    Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
                    break;
                }
            }
            Console.WriteLine("ThreadTwo Exit");

        }).Start();

        if (newEventWaitHandleObj)
        {
            Random ran = new Random();
            for (int i = 0; i < 8; i++)
            {

                Thread.Sleep(ran.Next(500, 1000));
                //通过Set向 AutoResetEvent 发信号以释放等待线程
                Console.WriteLine("Main thread send the signal");
                eventWaitHandle.Set();
            }
        }

        Console.Read();
    }
}

代码的输出为下,代码中通过OpenExisting方法尝试打开已存在的同步事件句柄,如果失败,就创建一个EventWaitHandle实例。

至于后面部分代码的工作原理,跟AutoResetEvent的例子完全一样。

接下来,我们找到工程生成的exe文件,然后同时启动两次exe文件,可以看到如下输出,后面启动的进程能够打开前面进程创建的同步事件句柄。通过这种方式,就可以实现进程之间的线程同步。

总结

本文介绍了WaitHandle类型,以及该类型的子类型EventWaitHandle,并且介绍了如何通过AutoResetEvent和ManualResetEvent进行线程同步。

AutoResetEvent和ManualResetEvent的区别:

  • AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为非终止状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程
  • ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为非终止状态。也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。

下一篇将继续介绍互斥体Mutex和信号量Semaphore的使用。

时间: 2024-08-10 20:11:30

线程同步 –AutoResetEvent和ManualResetEvent的相关文章

服务总结 -多线程 - 线程同步(AutoResetEvent与ManualResetEvent)

前言 在我们编写多线程程序时,会遇到这样一个问题:在一个线程处理的过程中,需要等待另一个线程处理的结果才能继续往下执行.比如:有两个线程,一个用来接收Socket数据,另一个用来处理Socket数据,而处理Socket数据的那个线程需要在接收到Socket数据后才能处理运行,就要等待接收线程接收数据.那么处理线程如何等待,接收线程又如何通知处理线程呢? 其中一个比较好的方式就是使用AutoResetEvent/ManualResetEvent 1. AutoResetEvent/ManualRe

C#线程同步AutoResetEvent类介绍

AutoResetEvent 可以从一个线程向另一个线程发送通知,可以通知等待的线程有某事件发生. 通俗的来讲只有等Set()成功运行后WaitOne()才能够运行 Set是发信号,WaitOne是等待信号 1 public class AutoResetEventTest 2 { 3 4 private AutoResetEvent autoReset = null; 5 6 public AutoResetEventTest(AutoResetEvent resetEvent) 7 { 8

【编写高质量代码C#】建议72:在线程同步中使用信号量

1.使用信号机制提供线程同步的一个简单例子 AutoResetEvent autoResetEvent = new AutoResetEvent(false); private void button1_Click(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; Thread tWork = new Thread(() => { label1.Text = "线程启动...&quo

C#多线程同步事件及等待句柄AutoResetEvent 和 ManualResetEvent

最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也简要提了一下System.Threading.WaitHandle.WaitOne .System.Threading.WaitHandle.WaitAny和System.Threading.WaitHandle.WaitAll ,下面我们一最初学者的角度来看,多线程之间的同步. 假设有这样的一个场

C# 使用ManualResetEvent 进行线程同步

上一篇我们介绍了AutoResetEvent,这一篇我们来看下ManualResetEvent ,顾名思义ManualResetEvent  为手动重置事件. AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别. Set方法将信号置为发送状态,Reset方法将信号置为不发送状态,WaitOne等待信号的发送.可以通过构造函数的参数值来决定其初始状态,若为true则非阻塞状态,为false为阻塞状态.如果某个线程调用WaitOne方法,则

线程同步之ManualResetEvent类的用法

笔者的一台激光测厚设备的软件, 它有一个运动线程, 一个激光数据处理线程. 运动线程做的事就是由A点移动到B点, 然后再由B点移动回A点. 激光处理线程要做的事就是采集指定数量点的激光数据, 随着采集的点数增加, 耗时也会增加. 这两个线程就存在线程同步的问题, 预想的标准流程应该是这样的: A点到B点运动开始一瞬间, 通知激光线程开始采集数据 激光线程采集数据完成后, 通知运动线程继续, 这时运动线程决定当前是由A到B, 还是由B到A 循环执行上述的动作. 这样的线程同步要求, 可以使用C#的

线程同步之-旋转门AutoResetEvent

主要作用:从一个线程向另一个线程发送通知,进行线程同步. 有点像旋转门,一次只允许一个人通过(一端等待,一端通过,如此循环) AutoResetEvent 常常被用来在两个线程之间进行信号发送,两个线程共享相同的AutoResetEvent对象,线程可以通过调用AutoResetEvent对象的WaitOne()方法进入等待状态,然后另外一个线程通过调用AutoResetEvent对象的Set()方法取消等待的状态. 使用场景:几个线程之间相互交替,或者以某种顺序完成任务时. 参考资源:http

利用C# AutoResetEvent进行线程同步

AutoResetEvent 允许线程通过发信号互相通信. 通常,当线程需要独占访问资源时使用该类. 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号. 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用. 调用 Set 向 AutoResetEvent 发信号以释放等待线程. AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态. 如果没有任何线程在

[.net]基元线程同步构造

1 /* 基元线程同步构造 2 用户模式构造: 3 易变构造(Volatile Construct) 4 互锁构造(Interlocked Construct):自旋锁(Spinlock) 乐观锁(Optimistic Concurrency Control,乐观并发控制) 5 内核模式构造: 6 事件构造(Event) 7 信号量构造(Semaphore) 8 互斥体构造(Mutex) 9 */ 10 11 //易变构造,Volatile.Write()之前的所有字段写入操作,必须再该方法调用