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

C# CountdownEvent 类 使用详解

总目录


前言

CountdownEvent 是 C# 中用于多线程协作的同步工具,位于 System.Threading 命名空间下。它提供了一种简单而有效的方式来等待多个并发操作完成。CountdownEvent 的核心思想是初始化一个计数器,在每个操作完成时减少该计数器,并在计数器归零时释放所有等待的线程。适用于需要等待多个并行操作完成的场景(例如分阶段任务、批量数据处理)。


一、核心概念

  • 计数器机制:初始化时指定一个初始计数值,每次调用 Signal() 方法减少计数器。当计数器归零时,所有等待的线程被唤醒。
    • 初始化计数器:通过构造函数或 InitialCount 属性设置初始计数值。
    • 递减计数器:每次调用 Signal() 方法时,计数器会递减。
    • 等待计数器归零:调用 Wait() 方法的线程会被阻塞,直到计数器变为0。
    • 手动重置:可以通过调用 Reset() 方法将计数器重置为任意值,以便重复使用。
  • 适用场景
    • 等待一组并行任务全部完成的场景,如并行计算、多任务处理等。
    • 分阶段任务的协调(如“所有子任务完成后进入下一阶段”)。
  • Task.WaitAll 的区别
    • CountdownEvent 更灵活,支持动态调整计数器。
    • Task.WaitAll 仅适用于 Task 对象,而 CountdownEvent 可与任何线程模型结合。

二、基本用法

1. 构造函数

var countdownEvent = new CountdownEvent(initialCount: 3); // 初始计数值为3

2. 关键方法

方法作用
Signal()减少计数器(原子操作),表示一个任务已经完成
返回 true 表示成功。若计数器已归零,返回 false
Wait()阻塞当前线程,直到计数器归零。
AddCount(int value)手动增加计数器(需确保当前未处于终止状态)。
Reset()重置计数器(需谨慎使用,可能破坏协作逻辑)。
Dispose()释放资源。
CurrentCount获取当前计数器的值。
InitialCount获取计数器的初始值。

三、示例

示例 1:等待多个任务完成

using System.Threading;

class Program
{
    static CountdownEvent countdown = new CountdownEvent(3); // 初始计数值3

    static void Main()
    {
        // 启动3个并行任务
        new Thread(DoWork).Start("Task 1");
        new Thread(DoWork).Start("Task 2");
        new Thread(DoWork).Start("Task 3");

        countdown.Wait(); // 阻塞直到所有任务完成
        Console.WriteLine("所有任务已完成!");
    }

    static void DoWork(object name)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"{name} 完成");
        countdown.Signal(); // 减少计数器
    }
}

输出

Task 1 完成
Task 3 完成
Task 2 完成
所有任务已完成!

示例 2:动态调整计数器

static CountdownEvent countdown = new CountdownEvent(2);

static void Main()
{
    // 初始需要等待2个信号
    new Thread(() => {
        Thread.Sleep(500);
        Console.WriteLine("任务A完成");
        countdown.Signal();
    }).Start();

    // 动态增加计数器(需确保未处于终止状态)
    countdown.AddCount(1); // 总计数器变为3

    new Thread(() => {
        Thread.Sleep(1000);
        Console.WriteLine("任务B完成");
        countdown.Signal();
    }).Start();

    new Thread(() => {
        Thread.Sleep(1500);
        Console.WriteLine("任务C完成");
        countdown.Signal();
    }).Start();

    countdown.Wait();
    Console.WriteLine("所有任务完成");
}

输出

任务A完成
任务B完成
任务C完成
所有任务完成

示例3:最佳实践-等待多个线程完成

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        int taskCount = 5;
        CountdownEvent countdown = new CountdownEvent(taskCount);

        Console.WriteLine("启动多个任务...");

        for (int i = 1; i <= taskCount; i++)
        {
            int taskId = i;
            Task.Run(() =>
            {
                try
                {
                    Console.WriteLine($"任务 {taskId} 开始执行...");
                    Thread.Sleep(1000); // 模拟任务执行时间
                    Console.WriteLine($"任务 {taskId} 完成");
                }
                finally
                {
                    countdown.Signal(); // 标记任务完成
                }
            });
        }

        Console.WriteLine("主线程等待所有任务完成...");
        countdown.Wait(); // 等待所有任务完成

        Console.WriteLine("所有任务已完成");
    }
}

四、注意事项

1. 资源释放

  • 使用 Dispose()using 块释放资源,避免句柄泄漏。
  • 在实际应用中,建议在 Signal() 调用周围添加 try-finally 块,以确保即使在任务抛出异常的情况下也能正确地递减计数器。
	using (var countdown = new CountdownEvent(3))
	{
    	// 使用 countdown
	}

2. 线程安全

  • Signal()AddCount() 是线程安全的,但需确保逻辑正确性。
  • 避免在计数器归零后继续调用 Signal()AddCount()(可能抛出 InvalidOperationException)。

3. 动态调整的陷阱

  • 调用 AddCount() 时需确保事件未终止(即计数器未归零),否则会抛出异常。
  • 示例错误代码:
    var countdown = new CountdownEvent(1);
    countdown.Signal(); // 计数器归零
    countdown.AddCount(1); // 抛出 InvalidOperationException
    

五、高级用法

1. 超时等待

bool completed = countdown.Wait(TimeSpan.FromSeconds(5));
if (!completed)
{
    Console.WriteLine("等待超时,仍有未完成的任务");
}

2. 结合异步编程

async Task RunAsync()
{
    var countdown = new CountdownEvent(3);
    var tasks = new Task[3];
    
    for (int i = 0; i < 3; i++)
    {
        tasks[i] = Task.Run(() => 
        {
            // 模拟工作
            Thread.Sleep(1000);
            countdown.Signal();
        });
    }

    await Task.Run(() => countdown.Wait());
    Console.WriteLine("所有异步任务完成");
}

六、常见问题

1. 问题:忘记调用 Signal()

  • 现象:主线程永久阻塞在 Wait()
  • 解决:确保每个子任务正确调用 Signal()

2. 问题:计数器归零后继续操作

  • 现象:调用 AddCount()Signal() 抛出异常。
  • 解决:检查计数器状态,或使用 TryAddCount() 方法:
    if (countdown.TryAddCount(1))
    {
        // 成功增加计数器
    }
    

七、替代方案

  • Task.WhenAll:更适合纯 Task 模型的异步操作。
  • Barrier:适用于分阶段的多线程协作(如循环执行多个阶段)。
  • SemaphoreSlim:控制并发线程数量,但逻辑不同。

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
xxx

相关文章:

  • 认识网络安全
  • 【css】width:100%;padding:20px;造成超出100%宽度的解决办法 - box-sizing的使用方法 - CSS布局
  • Android Studio:RxBus结合ICompositeSubscription使用
  • YOLO数据标注——LabelImg
  • PMP--冲刺--流程图
  • vue3+element-plus中的el-table表头和el-table-column内容全部一行显示完整(hook函数)
  • 【第3章:卷积神经网络(CNN)——3.8 迁移学习与微调策略】
  • 恩智浦:将开发文档迁移到DITA/XML
  • ASP.NET Core 使用 FileStream 将 FileResult 文件发送到浏览器后删除该文件
  • 趣味魔法项目 LinuxPDF —— 在 PDF 中启动一个 Linux 操作系统
  • jQuery UI 工作原理
  • C语言:指针详解
  • 深入了解 Oracle 正则表达式
  • 智能手表表带圆孔同心度检测
  • vue3:动态渲染后端返回的图片
  • 朝天椒USB服务器解决前置机U盾虚拟机远程连接
  • Python常见面试题的详解3
  • ES分词技术
  • SpringBoot:使用spring-boot-test对web应用做单元测试时如何测试Filter?
  • Redis 集群相关知识介绍
  • 五一档观众最满意《水饺皇后》
  • 牛市早报|“五一”假期预计跨区域人员流动量累计14.67亿人次
  • 禅定佛的微笑,从樊锦诗提到过的那尊说起
  • 贵州游船侧翻248名消防员已在搜救
  • 五一假期,新任杭州市委书记刘非到嘉兴南湖瞻仰红船
  • 魔都眼|上海环球马术冠军赛收官,英国骑手夺冠