C# Timer事件处理器异常触发问题及无额外变量实现方案咨询
Timer Elapsed事件异常触发问题分析与优化
问题场景
在C#(Unity环境)开发中遇到Timer的Elapsed事件处理器异常执行的问题:事件未按预期触发却出现批量执行的情况。
初始实现代码
private Queue<Command> _commands = new(1000); private Timer _timer = new(10000); private void Update() { Debug.Log(_commands.Count); _timer.Elapsed += OnReload; } private void OnReload(object sender, ElapsedEventArgs e) { _commands.Dequeue(); } private void Awake() { for (int i = 0; i < 1000; i++) { _commands.Enqueue(new FightingCommand()); } }
从Debug日志(
)可见队列计数下降速度远超预期,逻辑未达要求。
临时解决方法
通过新增布尔变量_isEnd中转事件触发逻辑,恢复了预期行为:
private Queue<Command> _commands = new(1000); private Timer _timer = new(10000); private bool _isEnd; private void Update() { Debug.Log(_commands.Count); _timer.Elapsed += OnReload; if (_isEnd) { _commands.Dequeue(); _isEnd = false; } } private void OnReload(object sender, ElapsedEventArgs e) { _isEnd = true; } private void Awake() { for (int i = 0; i < 1000; i++) { _commands.Enqueue(new FightingCommand()); } }
Debug日志(
)显示逻辑恢复正常。
异常原因
核心问题出在Update方法中重复订阅Elapsed事件:
- Unity的
Update是帧循环方法,每帧都会执行一次_timer.Elapsed += OnReload;,这会导致OnReload被多次注册到事件中。 - 当Timer到达触发时间时,所有已注册的
OnReload都会被执行,相当于一次Timer触发就执行N次Dequeue(N是累计的订阅次数),所以队列计数会快速下降,看起来像是事件"自行执行"。 - 此外,
System.Timers.Timer的Elapsed事件是在后台线程触发的,直接在OnReload中操作主线程的队列,还会引发线程安全问题,可能导致数据异常。
无需布尔变量的优化实现
解决思路是:只订阅一次事件,控制Timer的启停,并处理线程安全:
private Queue<Command> _commands = new(1000); private Timer _timer; // 线程锁,用于保护队列的并发访问 private readonly object _queueLock = new object(); private void Awake() { // 初始化Timer,设置首次触发延迟和周期,初始先停止 _timer = new Timer(10000); _timer.Elapsed += OnReload; _timer.AutoReset = true; // 如果需要重复触发则设为true,否则false _timer.Start(); // 初始化队列 for (int i = 0; i < 1000; i++) { _commands.Enqueue(new FightingCommand()); } } private void Update() { Debug.Log(_commands.Count); } private void OnReload(object sender, ElapsedEventArgs e) { lock (_queueLock) { if (_commands.Count > 0) { _commands.Dequeue(); } else { // 队列为空时停止Timer _timer.Stop(); } } } // 可选:场景销毁时清理Timer private void OnDestroy() { _timer?.Stop(); _timer?.Dispose(); }
优化要点
- 仅订阅一次事件:在
Awake中完成事件订阅,避免重复注册。 - 线程安全处理:使用
lock保护队列的操作,因为Timer的Elapsed事件在后台线程执行,而队列是在主线程初始化的,必须避免并发访问。 - 控制Timer生命周期:在合适的时机(如Awake)启动Timer,队列为空时停止,场景销毁时释放Timer资源。
- 移除Update中的冗余操作:Update只负责日志输出,不再处理事件订阅或队列操作,逻辑更清晰。
内容的提问来源于stack exchange,提问作者Kyzlyk
相关产品推荐
相关产品推荐

