Barrier适合多阶段协同,CountdownEvent仅等待N个独立操作完成;前者支持阶段回调、可重用、需SignalAndWait()同步进入下一阶段,后者无回调、归零后需Reset()才能复用。

Barrier 适合多阶段协同,CountdownEvent 只管“全部做完”
根本区别在于同步意图:Barrier 是为「分阶段并行」设计的,比如多个线程一起执行 Phase 1 → 全部到达后自动进入 Phase 2;而 CountdownEvent 是为「等待 N 个独立操作完成」设计的,它不关心阶段、不回调、不重用(除非手动 Reset()),只等计数归零就放行。
-
Barrier构造时指定参与者数量,每次调用SignalAndWait()表示“我这阶段干完了,等别人”,全员到齐才继续下一阶段 -
CountdownEvent构造时传入初始计数(如new CountdownEvent(5)),每个任务结束调一次Signal(),仅此而已 -
Barrier支持阶段回调(构造时传Action),CountdownEvent完全没有回调机制 -
Barrier可重用(每阶段自动递增CurrentPhaseNumber),CountdownEvent一旦归零就进入就绪态,再Wait()不阻塞——必须显式Reset(n)才能复用
Signal() 和 SignalAndWait() 的语义完全不同
别被名字误导:CountdownEvent.Signal() 就是简单减一;而 Barrier.SignalAndWait() 是原子操作:先发信号 + 立即阻塞,直到所有其他参与者也调了 SignalAndWait()。
-
CountdownEvent.Signal()可在任意位置安全调用(推荐放在finally块里防异常漏调) -
Barrier.SignalAndWait()必须成对出现在每个参与者的同一逻辑点,否则会死锁——比如一个线程在 Phase 1 调了,另一个却跳过直接进 Phase 2,前者永远卡住 -
Barrier还提供带超时的SignalAndWait(int timeout),返回false表示有人没按时到达;CountdownEvent.Wait()也有超时重载,但意义不同:只是防止无限等待
常见误用:把 CountdownEvent 当 Barrier 用
典型错误是想实现“所有线程做完第一件事,再一起做第二件事”,却只用 CountdownEvent ——它无法保证“同时出发”。你只能靠两次 Wait() + 两次 Reset() 模拟,但中间存在竞态:部分线程可能已开始第二件事,而另一些还在重置计数器。
- 正确做法:用
Barrier,天然支持多阶段同步,且SignalAndWait()保证所有线程在阶段边界严格对齐 - 如果硬要用
CountdownEvent模拟两阶段,必须加额外同步(如ManualResetEventSlim控制第二阶段启动),代码变复杂且易出错 -
CountdownEvent更适合场景:启动 10 个 HTTP 请求,主线程等全部响应返回再汇总;或异步文件写入,等所有FileStream.WriteAsync完成再关闭
Dispose() 和线程安全注意事项
两者都需显式释放资源,但风险点不同。
-
CountdownEvent.Dispose()后再调Wait()或Signal()会抛ObjectDisposedException;若不确定是否已释放,可用TryAddCount()替代AddCount()避免异常 -
Barrier.Dispose()后再调SignalAndWait()同样报错;但它还要求所有参与者必须在Dispose()前退出同步点,否则可能引发未定义行为 - 两者所有成员方法都是线程安全的,无需额外加锁;但
CountdownEvent.Reset()和Barrier的构造/销毁不能和活跃的Signal*操作并发
static void Example_CountdownEvent()
{
using var countdown = new CountdownEvent(2);
_ = Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("Task1 done"); countdown.Signal(); });
_ = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("Task2 done"); countdown.Signal(); });
countdown.Wait(); // 主线程阻塞至此
Console.WriteLine("All done");
}
static void Example_Barrier()
{
using var barrier = new Barrier(2, b => Console.WriteLine($"Phase {b.CurrentPhaseNumber} ended"));
_ = Task.Run(() =>
{
Console.WriteLine("Phase1 step1");
barrier.SignalAndWait();
Console.WriteLine("Phase2 step1");
barrier.SignalAndWait();
});
_ = Task.Run(() =>
{
Console.WriteLine("Phase1 step2");
barrier.SignalAndWait();
Console.WriteLine("Phase2 step2");
barrier.SignalAndWait();
});
}
最常被忽略的一点:不要在 Barrier 的回调(postPhaseAction)里做耗时操作,它会阻塞所有等待线程——这个回调是在最后一个到达者线程上下文中同步执行的。










