C# 线程同步基元-CountdownEvent
CountdownEvent-在收到特定次数信号后使等待线程继续运行的同步基元
1 CountdownEvent内部使用ManualResetEventSlim来实现信号量同步的
在CountdownEvent源码里有一个ManualResetEventSlim变量,源码如下:
2 CountdownEvent实例CurrentCount属性
CurrentCount属性:被阻止线程继续运行还需要收到的剩余信号量数
看一下源码就知道了:
其中的m_currentCount的值正常情况下就是CurrentCount属性Get的返回值
3 CountdownEvent实例Wait方法
Wait方法阻止当前线程,直到收到指定注册的信号量数后才会继续运行Wait后面的代码,Wait方法有一个以毫秒为单位超时参数,如果等待时间超过了这个时间,就继续运行Wait后面的代码,防止无限等待;如果Wait方法没有指定超时参数默认是无限等待(即超时参数设置为-1)
Wait方法源码如下:
可以看到,Wait阻止线程继续运行是通过ManualResetEventSlim的Wait方法来实现的,Wait方法是否阻塞其所在的线程是根据IsSet值来判断的,IsSet值获取方法源码如下:
4 CountdownEvent实例Signal()方法
CountdownEvent是通过ManualResetEventSlim的Set方法来通知被阻止的线程继续运行的
CountdownEvent的Signal方法用来向CountdownEvent实例注册信号并减少CurrentCount属性值,即注册了n个信号,则CurrentCount就会减少n,当CurrentCount<=0时被阻止线程就会继续运行
CountdownEvent的Signal()方法作用:向CountdownEvent注册1次信号,并将CountdownEvent.CurrentCount值原子减1
Signal(n):将CountdownEvent实例注册n个信号并将CurrentCount属性值减n
5 CountdownEvent使用示例
一般的使用步骤:在主调线程里定义一个多线程共用的同一个CountdownEvent实例,然后在多线程的运行方法里参数里接收这个CountdownEvent实例
多线程TaskMethod方法如下:
public static async Task<string> CountdownEventTaskMethod(string taskName, double seconds, CountdownEvent countdownEvent, CancellationToken cancellationToken) { await Task.Delay(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"注册信号前 [{taskName}] 继续运行还需要注册 [{ countdownEvent.CurrentCount }] 个信号量等待线程才会继续运行"); Console.WriteLine($"{taskName} 注册1次信号[Signal()]"); countdownEvent.Signal(); // 向CountdownEvent注册信号,并减少CountdownEvent.CurrentCount值 Console.WriteLine($"注册信号后 [{taskName}] 还需要注册 [{ countdownEvent.CurrentCount }] 个信号量等待线程才会继续运行"); return $"{taskName} ok"; }
测试方法代码如下:
public static void CountdownEventTest() { // CountdownEvent:是在收到信号特定次数后取消阻止等待线程的同步基元 // CountdownEvent:在计数变为0(即CurrentCount=0)时处于有信号状态的同步基元 CountdownEvent countdownEvent = new CountdownEvent(2);// 设置信号特定次数初始值为2 using (countdownEvent) { var task1 = CountdownEventTaskMethod("task1", 1, countdownEvent, default); var task2 = CountdownEventTaskMethod("task2", 2, countdownEvent, default); var task3 = CountdownEventTaskMethod("task3", 3, countdownEvent, default); var task4 = CountdownEventTaskMethod("task3", 4, countdownEvent, default); Console.WriteLine("开始等待收到特定次数信号"); // 阻止当前线程,直到收到信号特定次数(这里是2次)继续运行 // 也可以理解为countdownEvent.CurrentCount=0时继续运行 countdownEvent.Wait(); Console.WriteLine($"已收到了信号特定次数继续运行,countdownEvent 当前还需要注册的信号量数量[{countdownEvent.CurrentCount}]"); } }
启动方法:
static void Main(string[] args) { CountdownEventTest(); Console.Read(); }
运行结果:
微软官方文档
https://docs.microsoft.com/zh-cn/dotnet/standard/threading/countdownevent