autoresetevent 和 manualresetevent 是 .net 中用于线程同步的两个重要类,都继承自 eventwaithandle。它们的核心区别在于 “信号触发后是否自动重置状态”。
✅ 一句话总结区别:
autoresetevent:
信号触发(set)后,只允许一个等待线程通过,然后自动重置为非信号状态(false)。manualresetevent:
信号触发(set)后,所有等待线程都可以通过,并且保持信号状态(true),直到你手动调用 reset() 才关闭。
🔍 详细对比
| 特性 | autoresetevent | manualresetevent |
|---|---|---|
| 初始状态 | 可指定(默认 false) | 可指定(默认 false) |
| 调用 set() 后 | 允许一个等待线程继续执行,然后自动变为 false | 所有等待线程继续执行,并保持 true |
| 如何关闭信号 | 自动关闭(无需干预) | 必须显式调用 reset() |
| 类比 | 旋转门(一次只过一人) | 闸门(打开后所有人都能过,直到手动关上) |
| 典型用途 | 线程间一对一通知、生产者-消费者单次唤醒 | 多线程同时启动/停止、初始化完成广播 |
💡 代码示例说明
示例 1:autoresetevent—— 一次只唤醒一个线程
var are = new autoresetevent(false);
// 启动3个等待线程
for (int i = 0; i < 3; i++)
{
int id = i;
task.run(() =>
{
console.writeline($"线程 {id} 等待中...");
are.waitone(); // 阻塞直到收到信号
console.writeline($"线程 {id} 被唤醒!");
});
}
thread.sleep(1000);
console.writeline("主线程发出信号...");
are.set(); // ⚠️ 只会唤醒 one 线程!
thread.sleep(100);
are.set(); // 再唤醒一个
thread.sleep(100);
are.set(); // 再唤醒最后一个
输出(顺序可能不同):
线程 0 等待中...
线程 1 等待中...
线程 2 等待中...
主线程发出信号...
线程 1 被唤醒!
线程 0 被唤醒!
线程 2 被唤醒!
✅ 每次 set() 只放行一个线程,之后自动关闭。
示例 2:manualresetevent—— 一次性唤醒所有线程
var mre = new manualresetevent(false);
for (int i = 0; i < 3; i++)
{
int id = i;
task.run(() =>
{
console.writeline($"线程 {id} 等待中...");
mre.waitone();
console.writeline($"线程 {id} 被唤醒!");
});
}
thread.sleep(1000);
console.writeline("主线程发出信号...");
mre.set(); // 🔓 所有等待线程立即通过!
// 不需要多次 set,也不自动关闭
输出:
线程 0 等待中...
线程 1 等待中...
线程 2 等待中...
主线程发出信号...
线程 2 被唤醒!
线程 0 被唤醒!
线程 1 被唤醒!
✅ 一次 set(),全部放行!
❗ 如果之后再有线程调用 waitone(),也会立即通过(因为信号仍为 true),除非你调用 mre.reset()。
🛠 常见使用场景
✅autoresetevent适用场景:
- 生产者-消费者模型:每次生产一个数据,唤醒一个消费者。
- 线程池任务分发:一个任务就绪,通知一个工作线程处理。
- 精确控制单次唤醒。
✅manualresetevent适用场景:
- 程序初始化完成广播:等所有资源加载完,通知所有线程开始工作。
- 并行测试启动:让多个线程同时开始执行(避免先后差异)。
- 全局暂停/恢复控制:如游戏暂停系统。
// 初始化完成示例
var initcomplete = new manualresetevent(false);
task.run(() => {
loadconfig();
loaddatabase();
initcomplete.set(); // 所有准备工作完成
});
// 多个工作线程等待初始化完成
task.run(() => {
initcomplete.waitone(); // 等待初始化
dowork();
});
⚠️ 注意事项
- 性能:两者都是内核对象(kernel object),涉及用户态 ↔ 内核态切换,频繁使用有开销。高频场景可考虑
spinwait或monitor。 - 替代方案(.net core / .net 5+):
- 使用
taskcompletionsource<t>实现异步通知(更现代) - 使用
countdownevent、barrier等高级同步原语
- 使用
- 不要混淆
reset()行为:autoresetevent调reset()是多余的(它自己会重置)manualresetevent必须手动reset()才能再次阻塞线程
✅ 总结记忆口诀:
- auto → 自动关门(过一人,门自动关)
- manual → 手动关门(门开了,所有人能过,得你手动关)
选择哪个?
👉 需要逐个唤醒?用 autoresetevent
👉 需要集体唤醒?用 manualresetevent
到此这篇关于c# autoresetevent和manualresetevent的实现示例的文章就介绍到这了,更多相关c# autoresetevent和manualresetevent内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论