C#多线程系列之线程通知
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 继续运行,然后阻塞自己;
程序代码如下(运行后,请将键盘设置成英文输入状态再按下按键):
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(); } } }
解释
两个线程具有的功能:阻塞自己、解除另一个线程的阻塞。
用电影《最佳拍档》里面的一个画面来理解。
doone 、dotwo 轮流呼吸,不能自己控制自己呼吸,但自己能够决定别人呼吸。
你搞我,我搞你,就能相互呼吸了。
当然waitone()
也可以设置等待时间,如果 光头佬(doone) 耍赖不让 金刚(dotwo)呼吸,金刚等待一定时间后,可以强行荡动天平,落地呼吸。
注意,autorestevent 用得不当容易发生死锁。
另外 autorestevent 使用的是内核时间模式,因此等待时间不能太长,不然比较耗费 cpu 时间。
autoresetevent 也适合用于线程同步。
另外,线程中使用 waitone()
,另一个线程使用 set()
通知后, autoresetevent 对象会自动恢复非终止状态,不需要线程使用 reset()
。
到此这篇关于c#多线程系列之线程通知的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 详细聊聊sql中exists和not exists用法
下一篇: Redis实现排名功能的示例代码