C#多线程(6):线程通知

简介: C#多线程(6):线程通知

AutoRestEvent 类


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

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


其构造函数只有一个:

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


构造函数 说明
AutoResetEvent(Boolean) 用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例。

真糟糕的机器翻译。


常用方法

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


方法 说明
Close() 释放由当前 WaitHandle 占用的所有资源。
Reset() 将事件状态设置为非终止,从而导致线程受阻。
Set() 将事件状态设置为有信号,从而允许一个或多个等待线程继续执行。
WaitOne() 阻止当前线程,直到当前 WaitHandle 收到信号。
WaitOne(Int32) 阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔(以毫秒为单位)。
WaitOne(Int32, Boolean) 阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。
WaitOne(TimeSpan) 阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。
WaitOne(TimeSpan, Boolean) 阻止当前线程,直到当前实例收到信号为止,同时使用 TimeSpan 指定时间间隔,并指定是否在等待之前退出同步域。


一个简单的示例

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

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


.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 继续运行,然后阻塞自己;

微信图片_20220503125648.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();
            }
        }
    }

微信图片_20220503125653.gif


解释

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

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


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

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

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


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

另外 AutoRestEvent 使用的是内核时间模式,因此等待时间不能太长,不然比较耗费 CPU 时间。


AutoResetEvent 也适合用于线程同步。

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

相关文章
|
2月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
118 0
|
2月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
3月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
220 5
|
3月前
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
89 0
|
7月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
207 20
|
7月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
9月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
151 1
|
11月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
98 2
|
11月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
160 2
|
10月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
280 0