c# 非阻塞算法_C# 多线程系列(6)线程通知

本文介绍了C#中的AutoResetEvent类,用于线程间的同步和通知。通过示例展示了如何创建、设置和重置AutoResetEvent,以及如何避免死锁和无效信号问题。文章强调了正确使用AutoResetEvent以确保线程安全的重要性。
摘要由CSDN通过智能技术生成

回顾一下,前面 lock、Monitor 部分我们学习了线程锁,Mutex 部分学习了进程同步,Semaphor 部分学习了资源池限制。

这一篇将学习 C# 中用于发送线程通知的 AutoRestEvent 类。

AutoRestEvent 类

用于从一个线程向另一个线程发送通知。

微软文档是这样介绍的:表示线程同步事件在一个等待线程释放后收到信号时自动重置。

其构造函数只有一个:

构造函数里面的参数用于设置信号状态。

8773277202d744f50265aef9ae719b9a.png

真糟糕的机器翻译。

常用方法

AutoRestEvent 类是干嘛的,构造函数的参数又是干嘛的?不着急,我们来先来看看这个类常用的方法:

8c8c14fa7fbed4401a90309149830c29.png

一个简单的示例

这里我们编写一个这样的程序:

创建一个线程,能够执行多个阶段的任务;每完成一个阶段,都需要停下来,等待子线程发生通知,才能继续下一步执行。

  • .WaitOne() 用来等待另一个线程发送通知;
  • .Set() 用来对线程发出通知,此时 AutoResetEvent 变成终止状态;
  • .ReSet() 用来重置 AutoResetEvent 状态;
    class Program
    {
        // 线程通知
        private static AutoResetEvent resetEvent = new AutoResetEvent(false);

        static void Main(string[] args){
            // 创建线程
            new Thread(DoOne).Start();

            // 用于不断向另一个线程发送信号
            while (true)
            {
                Console.ReadKey();
                resetEvent.Set();           // 发生通知,设置终止状态
            }
        }

        public static void DoOne(){
            Console.WriteLine("等待中,请发出信号允许我运行");

            // 等待其它线程发送信号
            resetEvent.WaitOne();

            Console.WriteLine("\n     收到信号,继续执行");
            for (int i = 0; i 5; i++) Thread.Sleep(TimeSpan.FromSeconds(0.5));

            resetEvent.Reset(); // 重置为非终止状态
            Console.WriteLine("\n第一阶段运行完毕,请继续给予指示");

            // 等待其它线程发送信号
            resetEvent.WaitOne();
            Console.WriteLine("\n     收到信号,继续执行");
            for (int i = 0; i 5; i++) Thread.Sleep(TimeSpan.FromSeconds(0.5));

            Console.WriteLine("\n第二阶段运行完毕,线程结束,请手动关闭窗口");
        }
    }

解释一下

AutoResetEvent 对象有终止和非终止状态。Set() 设置终止状态,Reset() 重置非终止状态。

这个终止状态,可以理解成信号已经通知;非终止状态则是信号还没有通知。

注意,注意终止状态和非终止状态指的是 AutoResetEvent 的状态,不是指线程的状态。

线程通过调用 WaitOne() 方法,等待信号;

另一个线程可以调用 Set() 通知 AutoResetEvent 释放等待线程。

然后 AutoResetEvent 变为终止状态。

需要注意的是,如果 AutoResetEvent 已经处于终止状态,那么线程调用 WaitOne() 不会再起作用。除非调用Reset() 。

构造函数中的参数,正是设置这个状态的。true 代表终止状态,false 代表非终止状态。如果使用 new AutoResetEvent(true); ,则线程一开始是无需等待信号的。

在使用完类型后,您应直接或间接释放类型,显式调用 Close()/Dispose() 或 使用 using。当然,也可以直接退出程序。

需要注意的是,如果多次调用 Set() 的时间间隔过短,如果第一次 Set() 还没有结束(信号发送需要处理时间),那么第二次 Set() 可能无效(不起作用)。

复杂一点的示例

我们设计一个程序:

  • Two 线程开始处于阻塞状态;
  • 线程 One 可以设置线程 Two 继续运行,然后阻塞自己;
  • 线程 Two 可以设置 One 继续运行,然后阻塞自己;

8f2f7c0346c4f4f64ecdb008d05dae7e.png

程序代码如下(运行后,请将键盘设置成英文输入状态再按下按键):

    class Program
    {
        // 控制第一个线程
        // 第一个线程开始时,AutoResetEvent 处于终止状态,无需等待信号
        private static AutoResetEvent oneResetEvent = new AutoResetEvent(true);

        // 控制第二个线程
        // 第二个线程开始时,AutoResetEvent 处于非终止状态,需要等待信号
        private static AutoResetEvent twoResetEvent = new AutoResetEvent(false);

        static void Main(string[] args){
            new Thread(DoOne).Start();
            new Thread(DoTwo).Start();

            Console.ReadKey();
        }

        public static void DoOne(){
            while (true)
            {
                Console.WriteLine("\n① 按一下键,我就让DoTwo运行");
                Console.ReadKey();
                twoResetEvent.Set();
                oneResetEvent.Reset();
                // 等待 DoTwo() 给我信号
                oneResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("\n     DoOne() 执行");
                Console.ForegroundColor = ConsoleColor.White;
            }
        }

        public static void DoTwo(){
            while (true)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));

                // 等待 DoOne() 给我信号
                twoResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("\n     DoTwo() 执行");
                Console.ForegroundColor = ConsoleColor.White;

                Console.WriteLine("\n② 按一下键,我就让DoOne运行");
                Console.ReadKey();
                oneResetEvent.Set();
                twoResetEvent.Reset();
            }
        }
    }

ffa382bd5eba4c1bdb2ecb899dc13ceb.gif

解释

两个线程具有的功能:阻塞自己、解除另一个线程的阻塞。

用电影《最佳拍档》里面的一个画面来理解。

DoOneDoTwo 轮流呼吸,不能自己控制自己呼吸,但自己能够决定别人呼吸。

你搞我,我搞你,就能相互呼吸了。

a35da0b736be05ca2f5dde1e48498163.png

当然WaitOne() 也可以设置等待时间,如果 光头佬(DoOne) 耍赖不让 金刚(DoTwo)呼吸,金刚等待一定时间后,可以强行荡动天平,落地呼吸。

注意,AutoRestEvent 用得不当容易发生死锁。

另外 AutoRestEvent 使用的是内核时间模式,因此等待时间不能太长,不然比较耗费 CPU 时间。AutoResetEvent 也适合用于线程同步。

另外,线程中使用 WaitOne() ,另一个线程使用 Set() 通知后, AutoResetEvent 对象会自动恢复非终止状态,不需要线程使用 Reset()

﹏﹏﹏﹏

476a348713ce01def74bf0897d1e961c.png

e7dbc83f4afcc323880abb13ea74f9e7.png

f6fc3100560846ba05b05e54f628d4c8.png

df0c0a615db67969f6e5aee3f58f0cff.png 59faa320e870fada8efaae3b38e48dfe.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值