C# AutoResetEvent和ManualResetEvent

AutoResetEventManualResetEvent 是 .NET 中用于线程同步 的两个重要类,都继承自 EventWaitHandle。它们的核心区别在于 "信号触发后是否自动重置状态"


✅ 一句话总结区别:

  • AutoResetEvent

    信号触发(Set)后,只允许一个等待线程通过 ,然后自动重置为非信号状态(false)

  • ManualResetEvent

    信号触发(Set)后,所有等待线程都可以通过 ,并且保持信号状态(true) ,直到你手动调用 Reset() 才关闭。


🔍 详细对比

特性 AutoResetEvent ManualResetEvent
初始状态 可指定(默认 false 可指定(默认 false
调用 Set() 允许一个 等待线程继续执行,然后自动变为 false 所有 等待线程继续执行,并保持 true
如何关闭信号 自动关闭(无需干预) 必须显式调用 Reset()
类比 旋转门(一次只过一人) 闸门(打开后所有人都能过,直到手动关上)
典型用途 线程间一对一通知、生产者-消费者单次唤醒 多线程同时启动/停止、初始化完成广播

💡 代码示例说明

示例 1:AutoResetEvent ------ 一次只唤醒一个线程

csharp 复制代码
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 ------ 一次性唤醒所有线程

csharp 复制代码
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 适用场景:

  • 程序初始化完成广播:等所有资源加载完,通知所有线程开始工作。
  • 并行测试启动:让多个线程同时开始执行(避免先后差异)。
  • 全局暂停/恢复控制:如游戏暂停系统。
csharp 复制代码
// 初始化完成示例
var initComplete = new ManualResetEvent(false);

Task.Run(() => {
    LoadConfig();
    LoadDatabase();
    initComplete.Set(); // 所有准备工作完成
});

// 多个工作线程等待初始化完成
Task.Run(() => {
    initComplete.WaitOne(); // 等待初始化
    DoWork();
});

⚠️ 注意事项

  1. 性能 :两者都是内核对象(Kernel Object),涉及用户态 ↔ 内核态切换,频繁使用有开销。高频场景可考虑 SpinWaitMonitor
  2. 替代方案(.NET Core / .NET 5+)
    • 使用 TaskCompletionSource<T> 实现异步通知(更现代)
    • 使用 CountdownEventBarrier 等高级同步原语
  3. 不要混淆 Reset() 行为
    • AutoResetEventReset() 是多余的(它自己会重置)
    • ManualResetEvent 必须手动 Reset() 才能再次阻塞线程

✅ 总结记忆口诀:

  • Auto自动关门(过一人,门自动关)
  • Manual手动关门(门开了,所有人能过,得你手动关)

选择哪个?

👉 需要逐个唤醒 ?用 AutoResetEvent

👉 需要集体唤醒 ?用 ManualResetEvent

相关推荐
tianyu23440 分钟前
Java 正则表达式终极指南:从 Pattern 到 Matcher,从双重转义到性能优化,一篇全搞定
java·开发语言·正则表达式·group·find·pattern·matcher
Emily156853598703 小时前
Emerson 1C31129G03 Analog Input Module
java·开发语言·前端·plc·emerson·1c31129g03·input module
就叫_这个吧8 小时前
Java递归方法实现面包屑导航
java·开发语言
fīɡЙtīиɡ ℡8 小时前
AI 应用系统设计
java·开发语言·人工智能
城管不管8 小时前
重生——第十一次面试之挖财一面2026.8.19已OC
java·服务器·jvm·数据库·spring·面试·职场和发展
码匠许师傅8 小时前
【C++ 面试真题】26. 聊聊 C++ 的智能指针
java·c++·面试
AI绘画哇哒哒8 小时前
【建议收藏!】35岁后端血泪忠告,这3类人别硬转Agent(过来人亲述)
java·人工智能·后端·ai·程序员·大模型·agent
最强小杰9 小时前
gpt-5.6-sol 频繁报 503 怎么办?区分容量熔断和限速 429 的排查方法 + 可复用 retry wrapper
java·人工智能·gpt·ai
csdn_aspnet9 小时前
C# 在逗号分割的字符串中判断是否包含指定字符
c#·正则·linq·string·regex·contains
宝桥南山10 小时前
Blazor Web Assembly - 体验一下Authentication State从Server共享给Client
microsoft·微软·c#·asp.net·.net·.netcore