System.Timers.Timer多线程场景下lock语句使用正确性及多线程逻辑咨询
首先,先给你明确几个核心问题的结论,再结合你的代码场景拆解分析:
一、当前lock语句是必要的,核心思路也没问题
你遇到的「不加lock就抛异常」的问题,本质是多线程下的内存可见性问题:
_syncing被主线程(同步开始/结束时)修改,而Timer的OnElapsed是在ThreadPool线程执行的。如果不加lock,编译器或CPU可能会对_syncing的读写做优化,导致Timer线程读取到的是旧的false值,依然会去访问App.ActiveView.Id——而此时同步还没完全结束,这个属性不允许被访问,所以抛出异常。- 加了lock之后,会强制触发内存屏障,保证主线程修改的
_syncing值能立刻被Timer线程看到,这样Timer线程就会跳过访问ActiveView.Id的逻辑,避免异常。
二、各部分lock的作用与优化建议
1. 关于_syncing的lock
你的用法是正确的,但也有更轻量的替代方案:如果只是单个布尔值的读写,可以用volatile关键字标记_syncing:
private static volatile bool _syncing = false;
这样读写_syncing时不需要加lock,volatile同样能保证多线程下的内存可见性。不过如果以后你需要扩展同步相关的逻辑(比如不止修改一个布尔值),lock的灵活性会更高,当前的写法完全没问题。
2. 关于Activate/Deactivate里的lock
这部分lock也是必要的:
- 这两个方法修改
_isActive和_timer静态字段,而_timer同时会被OnElapsed线程操作(Stop/Start)。如果不加lock,可能出现竞态条件:比如Deactivate在OnElapsed的_timer.Stop()之后、_timer.Start()之前执行,直接Dispose了_timer,此时OnElapsed再调用_timer.Start()就会触发已释放对象的异常。
不过这里有个小隐患:你的OnElapsed里操作_timer时没有加lock,建议把_timer的Stop/Start也放到lock块里,同时在重启Timer前检查_isActive状态,防止操作已被Dispose的Timer:
private async static void OnTimerElapsed(object sender, ElapsedEventArgs e) { bool shouldProcess = false; lock (_lock) { if (_syncing || !_isActive) return; _timer.Stop(); // 先停Timer,避免重入 shouldProcess = true; } if (!shouldProcess) return; try { int activeViewId = App.ActiveView.Id; lock (_lock) { if (_lastId == activeViewId) return; _lastId = activeViewId; } await App.Dispatcher.BeginInvoke(() => { // 触发ViewChanged事件 }); } finally { lock (_lock) { if (_isActive) // 确保Timer仍处于激活状态才重启 { _timer.Start(); } } } }
3. 关于_lastId的线程安全
你当前的代码里读写_lastId没有加lock,这也存在可见性问题:Timer线程修改的_lastId可能无法被后续的Timer线程立刻看到,导致重复触发ViewChanged事件。上面的优化代码里已经把_lastId的读写放到了lock块里,解决了这个问题。
三、关于Timer重入的处理
你在OnElapsed里通过_timer.Stop()+_timer.Start()来避免Timer重入,这个思路是对的——因为System.Timers.Timer默认AutoReset=true,如果OnElapsed的处理时间超过Interval,会在ThreadPool上并发触发Elapsed事件,你的写法能保证上一次处理完成后才会触发下一次。
总结
你的核心逻辑是正确的,lock的使用是必要的,它解决了多线程下的可见性和竞态条件问题。按照上面的小优化调整后,代码的线程安全性会更完善,能彻底避免同步期间的异常和潜在的竞态问题。
内容来源于stack exchange

