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

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日志(未正常工作的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日志(正常工作的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:20:42