You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

.NET 3.5中Bar方法卡lock,Monitor.Pulse无法执行问题求助

问题分析与解决方案

首先咱们拆解下你的问题:FooBar作为外部设备事件处理器运行在不同线程,事件触发Foo后调用Bar会卡在lock(objLock),但手动从主程序按钮调用Foo却一切正常。核心矛盾在于事件触发的线程上下文里,锁没有被及时释放,导致Bar无法获取锁。

可能的问题点

  1. volatile修饰锁对象是多余且可能引发隐性问题的
    你给objLock加了volatile修饰符,但lock操作本身已经保证了内存可见性,而且volatile是用来修饰会被多线程修改的变量引用/值的——你的objLock只初始化了一次,后续没有重新赋值,完全不需要volatile。虽然理论上不会直接导致锁卡住,但这种冗余修饰可能在某些线程上下文里干扰CLR对同步块的处理逻辑。

  2. Foo方法中// doing something部分在事件线程中阻塞
    主程序调用Foo正常,说明这部分代码在UI线程(按钮触发一般是UI线程)里能顺利执行,但事件处理器的线程是外部设备驱动的线程,可能这部分代码依赖的资源在事件线程中处于阻塞状态(比如等待外部设备响应、陷入死循环,或者访问了只有UI线程能操作的控件),导致Foo一直没进入lock块,或者进入lock后迟迟没调用Monitor.Wait释放锁。这时候Bar尝试获取锁就会一直卡住。

  3. Wait/Pulse的调用时机存在隐性冲突
    Monitor.Wait会释放锁并等待Pulse,但如果BarFoo调用Wait之前就被触发,那Bar会卡在lock上,直到Foo调用Wait释放锁。如果Foo在事件线程中迟迟无法走到Wait步骤,就会出现你看到的持续卡住情况。

解决方案步骤

  • 第一步:移除objLockvolatile修饰符
    修改代码为:

    private object objLock = new object();
    

    锁对象不需要volatile,这是完全冗余的设置,还可能带来不必要的线程内存访问问题。

  • 第二步:排查Foo// doing something的执行逻辑
    在事件触发的线程中,这部分代码是否会阻塞?你可以在这部分代码前后加日志输出或者断点,看看事件线程执行到这里的时候是否卡住了。比如是否调用了同步IO操作、是否有死循环、是否直接访问了UI控件(跨线程访问UI会导致阻塞)。

  • 第三步:优化Bar的锁获取逻辑,避免无限等待
    可以用Monitor.TryEnter给锁获取设置超时时间,这样即使Foo一直持有锁,Bar也不会无限卡住,还能方便排查问题:

    void Bar() {
        if (Monitor.TryEnter(objLock, TimeSpan.FromSeconds(5))) {
            try {
                Monitor.Pulse(objLock);
            } finally {
                Monitor.Exit(objLock);
            }
        } else {
            // 处理获取锁超时的情况,比如记录日志、触发告警
        }
    }
    

额外提示

在.NET中使用Monitor.Wait/Pulse时,一定要遵守两个核心规则:

  • Wait和Pulse必须在同一个锁对象的lock块(或Monitor.Enter/Exit)内部调用
  • 等待的线程必须被正确唤醒,否则会一直处于等待状态(可以用Wait的重载版本设置超时时间,避免线程永久挂起)

内容的提问来源于stack exchange,提问作者Andis59

火山引擎 最新活动