monitor.wait 必须在 lock 块内调用,否则抛出 synchronizationlockexception;它会释放锁并挂起线程,唤醒后需重新竞争锁;必须用 while 循环检查条件,配合状态变量和 pulse/pulseall 正确使用。

Monitor.Wait 必须在 lock 块内调用,否则抛出 SynchronizationLockException
这是最常踩的坑:Monitor.Wait 不是“随便等一下”,它本质是释放当前线程持有的锁、挂起自己,并等待被唤醒。如果没持有锁就调用,运行时直接报 SynchronizationLockException。
正确姿势是:先用 lock(obj) 或 Monitor.Enter 获取锁,再调用 Monitor.Wait;它内部会自动释放该锁,并在被 Pulse 或 PulseAll 唤醒后、重新竞争获取同一把锁——注意,不是立刻恢复执行,而是要再次抢到锁才能继续往下走。
- 错误示例:
Monitor.Wait(someObj)单独写在外面 → 立即崩溃 - 正确结构:
lock (someObj) { Monitor.Wait(someObj); } - 若用
Monitor.Enter手动加锁,必须配对try/finally+Monitor.Exit,否则异常时锁不释放
Monitor.Pulse 只唤醒一个等待线程,且只对“已进入 Wait 队列”的线程有效
Monitor.Pulse 不是广播,也不保证唤醒谁。它只从当前对象的 wait queue 中挑一个线程(通常是等待最久的那个)移到 ready queue,让其有机会重新竞争锁。关键点在于:“Pulse 之前没有 Wait” = 白脉冲,什么也不会发生。
典型误用是生产者先 Pulse、消费者后 Wait,结果消费者永远卡住。因为 Pulse 发生时没人等着,信号丢失了。
- 必须严格遵循“先 Wait,后 Pulse”顺序(或至少确保 Wait 已启动)
- 若需唤醒所有等待者,改用
Monitor.PulseAll,比如多个消费者争抢同一资源 -
Pulse调用本身不要求持有锁?错!它也必须在lock块内,否则同样抛SynchronizationLockException
Wait/Pulse 组合常用于“生产者-消费者”场景,但需配合状态变量判断条件
Monitor.Wait 和 Pulse 本身不传递数据、也不校验业务逻辑。它们只是线程调度开关,真正决定“该不该等”“该不该醒”的,是你自己维护的状态变量(如队列是否为空、缓冲区是否满)。
常见错误是裸调 Wait,不检查条件,导致虚假唤醒(spurious wakeup)或逻辑错乱。.NET 的 Wait 设计允许唤醒后条件仍未满足,所以必须用 while 循环重检。
lock (queueLock)
{
while (messageQueue.Count == 0)
{
Monitor.Wait(queueLock);
}
var msg = messageQueue.Dequeue();
}
- 永远用
while包裹Wait,不用if - 状态变量(如
Count)必须在同一个锁保护下读写,否则有竞态 -
Pulse方通常在修改完状态后立即调用,例如Enqueue后Monitor.Pulse
Wait 可带超时,避免永久阻塞;但超时返回 false 不代表错误,只是“没等到”
Monitor.Wait(object, int) 和 Monitor.Wait(object, TimeSpan) 允许指定最大等待时间。超时后方法返回 false,线程恢复执行,但锁依然被持有(因为 Wait 是在 lock 块里释放并重入的)。
这适合做轮询、防死锁、或实现带截止时间的任务调度。但要注意:返回 false 是正常流程分支,不是异常,不应直接 throw。
- 超时后需手动检查状态是否满足,不满足可 continue 等下一轮,或 break 处理超时逻辑
- 超时值设太小会导致频繁空转,太大则响应延迟高;建议结合业务容忍度设定
- 不要在 Wait 超时后忘记处理未完成的业务路径(比如连接未建立就继续发请求)









