c# Barrier 和 CountdownEvent 的区别 c#多线程同步

来源:这里教程网 时间:2026-02-21 17:39:31 作者:

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
)里做耗时操作,它会阻塞所有等待线程——这个回调是在最后一个到达者线程上下文中同步执行的。

相关推荐