Barrier 适合多阶段协同,CountdownEvent 只管“全部做完”
根本区别在于同步意图:
Barrier是为「分阶段并行」设计的,比如多个线程一起执行 Phase 1 → 全部到达后自动进入 Phase 2;而
CountdownEvent是为「等待 N 个独立操作完成」设计的,它不关心阶段、不回调、不重用(除非手动
Reset()),只等计数归零就放行。
Barrier构造时指定参与者数量,每次调用
SignalAndWait()表示“我这阶段干完了,等别人”,全员到齐才继续下一阶段
CountdownEvent构造时传入初始计数(如
new CountdownEvent(5)),每个任务结束调一次
Signal(),仅此而已
Barrier支持阶段回调(构造时传
Action<barrier></barrier>),
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)里做耗时操作,它会阻塞所有等待线程——这个回调是在最后一个到达者线程上下文中同步执行的。
