【.NET】多线程:自动重置事件与手动重置事件的区别
在多线程编程中,如果每个线程的运行不是完全独立的。那么,一个线程执行到某个时刻需要知道其他线程发生了什么。嗯,这就是所谓线程同步。同步事件对象(XXXEvent)有两种行为:
1、等待。线程在此时会暂停运行,等待其他线程发出信号才继续(等你约);
2、发出信号。当前线程发出信号,其他正在等待线程收到信号后继续运行(我约你)。
从前,小明、小伟、小更、小红、小黄计划到野外去烤鱼吃。但他们只确定市郊东南方向的一片区域,并不能保证具体哪个地点适合烧烤。于是,他们商量好,大家同时从家里出发。小明离那里比较近,他先去考察一下;其他人到了东南郊后集合,等小明的消息。小明考察完毕,向大家群发消息说明选定的地点是F。最后大家继续前行,奔向F。
等待事件有好几个:
1、Mutex:互斥体。一次只能有一个线程获取到互斥体,其他线程只能等。占用互斥体的线程释放后,其他线程继续抢 Mutex。然后只有一个线程能抢到,其他线程继续等……
2、AutoResetEvent:自动事件,发出信号后立刻重置。
3、ManualResetEvent:手动事件,发出信号后不会立刻重置,得手动重置。
4、CountdownEvent:这个和上面两个差不多。但它会设定一个计数,线程发出信号时会减少计数。被阻止的线程要等到计数 <= 0 时才获得信号。
本次咱们讨论的重点是看看自动重置信号和手动重置信号之间有什么区别。
先看看自动重置的。
internal class Program
{ static AutoResetEvent theEvent = new(false); static void Main(string[] args)
{
// 启动三个线程
ThreadPool.QueueUserWorkItem(DoWorking, "A");
ThreadPool.QueueUserWorkItem(DoWorking, "B");
ThreadPool.QueueUserWorkItem(DoWorking, "C");
// 主线程监听键盘消息
while(true)
{
var keyInfo = Console.ReadKey(true);
// 看看是不是Y键
if(keyInfo.Key == ConsoleKey.Y)
{
// 点亮信号
theEvent.Set();
}
// 输出一行,方便判断一个循环
Console.WriteLine("------------------------------");
}
} static void DoWorking(object? state)
{
while(true)
{
// 等待主线程的信号
// 此线程会暂停
theEvent.WaitOne();
// 得到信号了,继续运行
Console.WriteLine("{0}已收到通知", state);
}
}
}
这个例子创建了三个线程,这里我用的是线程池,把一个WaitCallback委托传给 QueueUserWorkItem 方法就可以在线程池中运行新线程。上面示例中绑定的方法是 DoWorking。
AutoResetEvent 类的构造函数传了一个 bool 值,它的作用是设置等待事件的初始状态:
1、如果为 true,表示事件初始状态为打开信号,这会使正在等的线程马上得到信号;
2、如果为 false,表示事件的初始状态为没有信号,正在等待的线程继续等。
按照咱们这个例子的实际情况,我们一开始应该让事件无状态,让后台的三个线程等待。主线程读取按键信息,如果按的是【Y】键,那么事件调用 Set 方法,打开信号。此时,等得花儿都谢了的三个线程会继续。我们运行一下,看看能否符合预期。

经测试,我们会发现:每次按【Y】后,三个线程中只有一个获得信号并继续,其他两个还在高速上堵车。 AutoResetEvent 的自动重置就是打开信号后又立马关闭,每次只让一个线程收到信号。所以,当咱们按一次【Y】键后,主线程发出了信号,又马上关闭。三个后台线程相互竞争,随机获得机会,结束等待并继续运行。
手动重置事件在打开信号后,信号会持续有效,直到调用 Reset 方法手动关闭信号。手动重置信号能让多个线程有足够的时间收到信号。
下面咱们把上面的示例改为使用 ManualResetEvent 类。
internal class Program
{
static ManualResetEvent theEvent = new(false); static void Main(string[] args)
{
// 启动三个线程
ThreadPool.QueueUserWorkItem(DoWorking, "A");
ThreadPool.QueueUserWorkItem(DoWorking, "B");
ThreadPool.QueueUserWorkItem(DoWorking, "C");
// 主线程监听键盘消息
while(true)
{
var keyInfo = Console.ReadKey(true);
// 看看是不是Y键
if(keyInfo.Key == ConsoleKey.Y)
{
// 点亮信号
theEvent.Set(); // 持续一段时间后关闭信号
Thread.Sleep(3);
theEvent.Reset();
}
// 输出一行,方便判断一个循环
Console.WriteLine("------------------------------");
}
} static void DoWorking(object? state)
{
while(true)
{
// 等待主线程的信号
// 此线程会暂停
theEvent.WaitOne();
// 得到信号了,继续运行
Console.WriteLine("{0}已收到通知", state);
}
}
}
然后运行程序,这一次按下【Y】键后,三个线程都能收到信号通知了。

你会发现,有些线程重复了多次,那是因为 DoWorking 方法里面是个死循环。当信号持续打开期间,三个线程都有机会收到信号,甚至会重复收到。
上面的东东纯属演示,实际使用的话不会这样设计。最好的方法是建一个列表对象,主线程接收到的按键字符存放到一个列表中,然后,后台线程不断地从列表中取出元素来处理。这样设计程序会更流畅。
internal class Program
{
#region 字段区域
static Queue<char> keyChars = new();
#endregion static void Main(string[] args)
{
// 启动三个线程
ThreadPool.QueueUserWorkItem(DoSomething, "A");
ThreadPool.QueueUserWorkItem(DoSomething, "B");
ThreadPool.QueueUserWorkItem(DoSomething, "C"); while(true)
{
// 读取键盘字符
ConsoleKeyInfo info = Console.ReadKey(true);
// 将字符放入队列
keyChars.Enqueue(info.KeyChar);
}
} static void DoSomething(object? state)
{
while(true)
{
// 锁定
Monitor.Enter(keyChars);
if (keyChars.Count > 0)
{
// 取掉一个元素
char c = keyChars.Dequeue();
Console.WriteLine($"线程【{state}】获得字符:{c}");
}
// 解锁
Monitor.Exit(keyChars);
}
}
}
这里我用泛型队列 Queue<T> 来存放键盘敲入的字符,DoSomething 方法将放入线程池中运行。在从队列中取出元素并处理时,一定要记得上锁。我用的是 Monitor 对象的静态方法来上锁和解锁,当然你可以用 lock 语句块。
lock(keyChars)
{
……
}
如果不上锁,线程间在抢占资源时会导致不一致的状态。当A线程访问 keyChars.Count 属性时得到 1,还是 > 0 的,但在取出最后一个元素前,偏偏B线程动作快把最后一个元素拿走了。当A线程执行到 keyChars.Dequeue() 一句时,keyChars 队列中已经没有元素了,会发生错误。
主线程在 Enqueue 时并不需要锁定,因为元素送入队列只有一个线程在做,没人跟他抢资源,可以不锁定。
运行程序后,可以按字母、数字等按键来测试。毕竟像【F3】、【Ctrl】等按键获取到的是空白 char。

这样就顺畅很多了。
【.NET】多线程:自动重置事件与手动重置事件的区别的更多相关文章
- [一个经典的多线程同步问题]解决方案二:Event事件
使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步.本篇介绍用事件Event来尝试解决这个线程同步问题. 首先介绍下如何使用事件.事件E ...
- 秒杀多线程第六篇 经典线程同步 事件Event
原文地址:http://blog.csdn.net/morewindows/article/details/7445233 上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权” ...
- 转--- 秒杀多线程第六篇 经典线程同步 事件Event
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...
- C# 并行编程 之 轻量级手动重置事件的使用
目录(?)[-] 简单介绍 使用超时和取消 跨进程或AppDomain的同步 简单介绍 如果预计操作的等待的时间非常短,可以考虑使用轻量级的手动重置事件,ManualResetEventSlim. ...
- 关于SpringKafka消费者的几个监听器:[一次处理单条消息和一次处理一批消息]以及[自动提交offset和手动提交offset]
自己在使用Spring Kafka 的消费者消费消息的时候的实践总结: 接口 KafkaDataListener 是spring-kafka提供的一个供消费者接受消息的顶层接口,也是一个空接口; pu ...
- jQuery 学习笔记(5)(事件绑定与解绑、事件冒泡与事件默认行为、事件的自动触发、自定义事件、事件命名空间、事件委托、移入移出事件)
1.事件绑定: .eventName(fn) //编码效率略高,但部分事件jQuery没有实现 .on(eventName, fn) //编码效率略低,所有事件均可以添加 注意点:可以同时添加多个相同 ...
- 新引入thinkphp报错“应用目录[./Application/]不可写,目录无法自动生成! 请手动生成项目目录~”
新引入thinkphp报错“应用目录[./Application/]不可写,目录无法自动生成! 请手动生成项目目录~”, 其主要原因是文件夹的权限问题,手动将项目文件夹权限更改为可读可写就OK,具体操 ...
- PowerBuilder中DW如何手动触发事件
调用setitem默认不会触发itemchanged事件 如果想实现可手动触发itemchanged事件 事件格式如下: dw_list.event itemchanged( /*long row*/ ...
- springboot自动配置原理以及手动实现配置类
springboot自动配置原理以及手动实现配置类 1.原理 spring有一个思想是"约定大于配置". 配置类自动配置可以帮助开发人员更加专注于业务逻辑开发,springboot ...
- [JavaScript] JavaScript事件注册,事件委托,冒泡,捕获,事件流
面试题 event 事件 事件委托是什么? 如何阻止事件冒泡,阻止默认事件呢? Javascript 的事件流模型都有什么? 事件绑定和普通事件有什么区别? Event 对象 Event 对象,当事件 ...
随机推荐
- quarkus实战之四:远程热部署
将本地的改动极速同步到远程服务端,并自动生效,掌握此技能,开发调试会更高效 欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/ ...
- grafana 容器无法启动,打印权限问题
报错日志 open /var/lib/grafana/alerting/1/notifications: permission denied 问题原因 sudo chown -R docker: /v ...
- chrome浏览器插件react devtools、redux devtools,无需安装、解压即可用
react devtools用于调试react代码,可以查看到props.state的值,以及定义的hooks,而redux devtools可以追踪到action的派发.store的变化,两个都是r ...
- 新一代开源流数据湖平台Apache Paimon入门实操-下
@ 目录 实战 写表 插入和覆盖数据 更新数据 删除数据 Merge Into 查询表 批量查询 时间旅行 批量增量查询 流式查询 时间旅行 ConsumerID 查询优化 系统表 表指定系统表 分区 ...
- AVR汇编(三):寻址方式
AVR汇编(三):寻址方式 AVR具有多种寻址方式,在介绍具体的汇编指令之前,有必要对它们做一定了解. 前面介绍过,AVR将内存空间分为多个部分:寄存器堆.I/O空间.数据空间.程序空间.这些空间支持 ...
- 质效提升 | QA不做业务需求测试,你怎么看?
因为有的小伙伴看到公司的QA不测试业务需求,只搞流程.卡点.规范.技术创新.QA平台,行业洞察,让研发自测.研发担责上线bug和风险,所以问我,你怎么看QA不做业务需求测试这件事.其实我怎么看不重要 ...
- 使用 AutoGPTQ 和 transformers 让大语言模型更轻量化
大语言模型在理解和生成人类水平的文字方面所展现出的非凡能力,正在许多领域带来应用上的革新.然而,在消费级硬件上训练和部署大语言模型的需求也变得越来越难以满足. Hugging Face 的核心使命是 ...
- 一次搞定:借助Hutool封装代码快速解决webservice调用烦恼
前言 相信很多同行哪怕学了许多主流技术,但工作上依然免不了和传统企业打交道,而这样的企业往往还在用webservice做接口交互. 本文是作者近两年和医疗行业的厂家打交道研究出来的一点调用webser ...
- 论文解读(PERL)《PERL: Pivot-based Domain Adaptation for Pre-trained Deep Contextualized Embedding Models》
Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:PERL: Pivot-based Domain Adaptation for Pre-trained D ...
- Android13深入了解 Android 小窗口模式和窗口类型
Android13深入了解 Android 小窗口模式和窗口类型 小窗模式,作为一种在移动设备上的多任务处理方式,为用户带来了便捷和高效的体验,尤其在一些特定场景下,其价值愈发凸显.以下是为什么需要小 ...