【.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 对象,当事件 ...
随机推荐
- 2023牛客暑期多校训练营2 DEFGHIK
比赛链接 D 题解 知识点:贪心. 首先,因为第一个人喜欢吃的可能会被后面的人选中,因此直接选最喜欢吃的可能会浪费机会.所以,我们考虑先看后面的人怎么选,就是倒着贪心,我们考虑证明. 假设当前剩下的菜 ...
- zabbix system.run
Zabbix的system.run是一个主动模式的监控项,它允许用户在Zabbix服务器上执行自定义的命令或脚本,并获取执行结果作为监控数据. system.run监控项的配置包括以下几个关键参数: ...
- 一个从文件中过滤指定字符串的python3脚本
from tabulate import tabulate plugin = [ ... ] plugin_v1 = [ ... ] filepath = "E:\\PycharmProje ...
- 记一次 HTTPS 抓包分析和 SNI 的思考
日常听说 HTTPS 是加密协议,那现实中的 HTTPS 流量,是真的完全加密吗? --答案是,不一定.原因嘛,抓个包就知道了. 我们用 curl 命令触发一下: curl -v 'https://s ...
- 2023牛客暑期多校训练营5 ABCDEGHI
比赛链接 A 题解 知识点:莫队,树状数组. 区间询问显然可以离线莫队,考虑端点移动对答案的影响. 不妨先考虑右端点右移一个位置,对答案的改变.假设右端点右移后在 \(r\) ,我们先要知道 \([l ...
- 推荐一个高效解压缩工具 - XZ
前情提要:前段时间搞某较大型项目的性能测试,使用的是Oracle数据库.压测阶段搞的业务基础数据和压测数据耗时耗力,想把数据导出,后续有类似项目,可以导入复用. 遇到问题:通过数据泵方式导出的数据库文 ...
- 基于 ASP.NET 的投票系统
OnlineVoting 基于 ASP.NET 的投票系统 功能页面 登录 注册 首页 投票广场 查看别人发布的投票. 个人中心 个人资料 换头像.修改密码和其他信息. 发布投票 项目地址:https ...
- 7.1 C++ STL 非变易查找算法
C++ STL 中的非变易算法(Non-modifying Algorithms)是指那些不会修改容器内容的算法,是C++提供的一组模板函数,该系列函数不会修改原序列中的数据,而是对数据进行处理.查找 ...
- MIT6.s081/6.828 lectrue5/6:System call entry/exit 以及 Lab4 心得
这篇博客主要复习 lecture05:GDB calling conentions 和 lecture06:System call entry/exit 的内容,外加 Lab4:traps 的心得 前 ...
- java与es8实战之五:SpringBoot应用中操作es8(带安全检查:https、账号密码、API Key)
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<java与es8实战>系 ...