You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Callback测试报Monitor同步异常的原因及代码改写方案问询

异常原因

该异常确实是Monitor锁的线程亲和特性导致的:

  • Monitor的锁是和调用Enter获取锁的线程强绑定的,只有持有锁的线程才能调用对应对象的Exit释放锁,且Enter/Exit必须在同一线程成对出现
  • 你的代码中Monitor.Enter(callbackObjects)是在主线程(Main方法执行线程)调用的,而Monitor.Exit(callbackObjects)是在异步回调的工作线程中执行的,回调线程从未获取过该对象的锁,调用Exit自然会抛出SynchronizationLockException。

除此之外原代码还有两个逻辑问题:

  • 多线程下对List<int>的Add操作、Count读取没有同步保护,存在竞态条件,可能出现数据错乱
  • Monitor.Wait需要持有锁的线程调用Pulse/PulseAll才能唤醒,直接在其他线程调用Exit既不合法,也无法唤醒主线程的等待,就算不抛异常主线程也会等满10秒超时。

正确实现代码

跨线程完成通知优先用ManualResetEventSlim实现,逻辑清晰且不会出现锁线程亲和的问题,同时对共享资源加锁保证线程安全:

class Program
{
    // 专用锁对象,保护共享集合的读写
    private static readonly object _listLock = new object();
    static List<int> callbackObjects = new List<int>();
    static int expectedNumberOfCallbacks;
    // 线程同步事件,用于回调完成通知
    private static readonly ManualResetEventSlim _completeSignal = new ManualResetEventSlim(false);

    static void Main(string[] args)
    {
        expectedNumberOfCallbacks = 10;
        CallbackTest.CallbackTester.TriggerCallbacks(expectedNumberOfCallbacks, Callback);
        
        // 等待回调完成信号,超时时间设为10秒
        _completeSignal.Wait(TimeSpan.FromSeconds(10));
        
        // 加锁读取集合实际数量,保证读到最新值
        int actualCount;
        lock (_listLock)
        {
            actualCount = callbackObjects.Count;
        }

        Assert.AreEqual(actualCount, expectedNumberOfCallbacks);
        _completeSignal.Dispose();
    }

    private static void Callback(int callbackObject)
    {
        lock (_listLock)
        {
            callbackObjects.Add(callbackObject);
            Console.WriteLine($"Callback number {callbackObjects.Count}");
            // 达到预期回调数时发出完成信号
            if (callbackObjects.Count == expectedNumberOfCallbacks)
            {
                _completeSignal.Set();
            }
        }
    }
}

实现说明

  • 所有对共享集合callbackObjects的读写都放在lock块内,避免多线程操作集合的竞态问题,锁的获取和释放由lock语法糖自动在当前线程完成,不会出现跨线程释放锁的异常
  • ManualResetEventSlim是专门用于跨线程状态通知的同步原语,不绑定线程:回调线程凑够预期数量的回调时调用Set()将信号置为触发状态,主线程的Wait()会立即返回;如果10秒内没收到信号,Wait也会自动超时返回,完全符合需要的执行逻辑
  • Wait返回后不管是超时还是收到完成信号,都会执行断言校验,符合需求。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:57:11