当前位置: 首页 > news >正文

C# 的 ManualResetEvent(线程同步操作) 类详解

C# 的 ManualResetEvent 类详解

作用

ManualResetEvent 是用于线程同步操作的类,允许一个或多个线程等待特定信号,以协调多个线程的执行顺序。它通过事件通知机制实现,确保线程在收到信号前保持阻塞,直到其他线程显式发出信号。

核心功能
  • 阻塞线程:调用 WaitOne() 使线程进入等待状态。

  • 发送信号:调用 Set() 将事件设为终止状态,释放所有等待线程。

  • 重置信号:调用 Reset() 将事件恢复为非终止状态。


信号状态:
  • 终止状态:所有调用 WaitOne(),线程不会被阻塞,直到调用Reset()。
  • 非终止状态:所有调用 WaitOne(),线程会被阻塞,直到调用set()。

特点
  1. 手动重置:调用 Set() 后需手动调用 Reset() 才能将状态恢复为非终止状态。

  2. 多线程释放:一旦处于终止状态(Signaled),所有等待线程立即释放,直到手动重置。

  3. 线程安全:所有方法(SetResetWaitOne)都是线程安全的。

  4. 跨进程支持:可通过命名方式在进程间同步(构造函数传名称)。


应用场景
  1. 初始化同步:主线程等待子线程完成初始化后再继续。

  2. 资源就绪通知:多个工作线程等待某个共享资源(如数据加载完成)。

  3. 阶段化任务:分阶段任务中,后续阶段需等待前一阶段所有线程完成。

  4. 高并发控制:替代锁机制,允许多个线程同时访问资源(需配合 Reset())。


基础用法
  1. 初始化:创建实例,参数指定初始状态(true 为终止状态)。

    ManualResetEvent mre = new ManualResetEvent(false); // 初始为非终止
  2. 阻塞线程:调用 WaitOne()

    mre.WaitOne(); // 阻塞,直到事件变为终止状态
  3. 发送信号:调用 Set()

    mre.Set(); // 设置为终止状态,释放所有等待线程
  4. 重置信号:调用 Reset()

    mre.Reset(); // 恢复为非终止状态


代码实例

场景 1:主线程等待子线程完成

用途

主线程需要等待子线程完成某个任务后再继续执行。例如:主线程启动后台任务后需等待其初始化完成,再执行后续操作。

代码逻辑
class Example
{
    static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        Console.WriteLine("主线程启动工作线程。");
        Thread worker = new Thread(DoWork);
        worker.Start();

        Console.WriteLine("主线程等待信号...");
        mre.WaitOne(); // 阻塞主线程,直到子线程调用 Set()
        Console.WriteLine("主线程恢复执行。");
    }

    static void DoWork()
    {
        Console.WriteLine("工作线程执行任务...");
        Thread.Sleep(2000); // 模拟耗时操作
        mre.Set(); // 发送信号,唤醒主线程
    }
}
执行流程
  1. 主线程创建 ManualResetEvent 并初始化为非终止状态 (false)。

  2. 主线程启动子线程 DoWork

  3. 主线程调用 mre.WaitOne() 进入阻塞状态。

  4. 子线程执行任务(模拟耗时操作),完成后调用 mre.Set(),将事件状态设为终止。

  5. 主线程从 WaitOne() 处解除阻塞,继续执行后续代码。

输出结果
主线程启动工作线程。
主线程等待信号...
工作线程执行任务...
(等待 2 秒后)
主线程恢复执行。


场景 2:多个线程等待同一事件

用途

多个工作线程需要等待某个公共条件(如资源初始化完成)满足后,才能同时开始工作。例如:多个线程需等待数据库连接池初始化完成后才可执行查询。

代码逻辑
using System;
using System.Threading;

class Example
{
    static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        // 启动 3 个工作线程
        for (int i = 0; i < 3; i++)
        {
            new Thread(Worker).Start(i);
        }

        // 启动初始化线程
        new Thread(Initialize).Start();
    }

    static void Initialize()
    {
        Console.WriteLine("初始化开始...");
        Thread.Sleep(3000);
        Console.WriteLine("初始化完成!");
        mre.Set(); // 通知所有等待线程
    }

    static void Worker(object id)
    {
        Console.WriteLine($"线程 {id} 等待初始化...");
        mre.WaitOne(); // 阻塞,直到初始化线程调用 Set()
        Console.WriteLine($"线程 {id} 开始工作。");
    }
}
执行流程
  1. 主线程启动 3 个工作线程和一个初始化线程。

  2. 每个工作线程调用 mre.WaitOne() 进入阻塞状态,等待初始化完成。

  3. 初始化线程执行耗时操作(如加载配置),完成后调用 mre.Set()

  4. 所有等待的工作线程同时被唤醒,开始执行后续任务。

输出结果
线程 0 等待初始化...
线程 1 等待初始化...
线程 2 等待初始化...
初始化开始...
(等待 3 秒后)
初始化完成!
线程 0 开始工作。
线程 1 开始工作。
线程 2 开始工作。


场景 3:重复使用 ManualResetEvent

用途

需要多次复用同一个 ManualResetEvent 实例,分阶段同步多个任务。例如:分批次处理数据,每批任务完成后触发下一批任务。

代码逻辑
using System;
using System.Threading;

class Example
{
    static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        // 首次使用
        new Thread(() => Task("任务1")).Start();
        mre.WaitOne();
        mre.Reset(); // 重置为非终止状态

        // 第二次使用
        new Thread(() => Task("任务2")).Start();
        mre.WaitOne();
    }

    static void Task(string name)
    {
        Console.WriteLine($"{name} 进行中...");
        Thread.Sleep(1000);
        mre.Set();
    }
}
执行流程
  1. 主线程启动第一个子线程执行 任务1

  2. 主线程调用 mre.WaitOne() 阻塞,等待 任务1 完成。

  3. 子线程 任务1 完成后调用 mre.Set(),主线程恢复执行。

  4. 主线程调用 mre.Reset() 将事件重置为非终止状态。

  5. 主线程启动第二个子线程执行 任务2,再次调用 mre.WaitOne() 阻塞。

  6. 子线程 任务2 完成后调用 mre.Set(),主线程恢复执行。

输出结果
任务1 进行中...
(等待 1 秒后)
任务2 进行中...
(等待 1 秒后)

三个场景关键区别总结

场景核心目的ManualResetEvent 操作要点
主线程等待子线程单向等待子线程完成子线程完成时调用 Set()
多线程等待同一事件广播式唤醒所有等待线程Set() 后无需立即 Reset()
重复使用事件对象分阶段同步任务每次使用后需调用 Reset() 重置状态

通过这三个场景,可以灵活掌握 ManualResetEvent 在不同线程同步需求中的使用技巧。

相关文章:

  • C++————快慢双指针寻找链表循环
  • 国家网络安全事件应急预案
  • mapbox高阶,结合threejs(threebox)添加extrusion挤出几何体,并添加侧面窗户贴图和楼顶贴图,同时添加真实光照投影
  • 我与DeepSeek读《大型网站技术架构》(13)- 大型网站典型故障案例分析
  • 【MyBatis Plus JSON 处理器简化数据库操作】
  • 手写svm primal form形式
  • windows下搭建postgresql的流式数据库vector
  • 技术速递|.NET 9 中的 .NET MAUI 性能特性
  • AWS云编排详解-Cloud Formation
  • 个性化音乐推荐系统
  • 【Linux】centos配置可用的yum源
  • 影刀RPA安装32位与64位的差别
  • SpringBoot第一天
  • Spring MVC面试题(一)
  • 基于Spring Boot的网上蛋糕售卖店管理系统的设计与实现(LW+源码+讲解)
  • IIC通信协议详解与STM32实战指南
  • MySQL数据库复制
  • 轻量级嵌入式WebRTC开发:音视频通话EasyRTC纯C语言实现SFU/MCU架构与QoS优化
  • ENSP实验案例-企业局域网搭建实施设计方案(计算机毕业设计作品)
  • 使用 Golang 操作 MySQL
  • 欧洲观察室|欧盟对华战略或在中欧建交50年时“低开高走”
  • 三人在共享单车上印小广告被拘,北京警方专项打击非法小广告
  • 陈刚:推动良好政治生态和美好自然生态共生共优相得益彰
  • 上市公司重大资产重组新规九要点:引入私募“反向挂钩”,压缩审核流程
  • 《五行令》《攻守占》,2个月后国博见
  • 习近平向多哥新任领导人致贺电