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

链表中使用静态Timer触发多次Elapsed事件致空引用异常排查

问题根源:重复绑定Timer事件处理器

你遇到的NullReferenceException和事件多次触发的核心原因是每次调用SetTimerToFirst()时,都重复给_timer.Elapsed添加了Completed事件处理器,导致同一个事件被绑定了多次,进而在Timer触发时执行多次Completed方法——即使链表已经为空,旧的事件处理器依然会执行并尝试访问空的链表头部。

具体触发流程(以2个元素为例)

假设链表中有两个元素A(较早)和B(较晚):

  1. 第一次添加A时,调用SetTimerToFirst(),给_timer.Elapsed绑定1次Completed方法,Timer开始计时。
  2. A的时间到了,Elapsed触发,执行1次Completed:停止Timer,打印A的Id,移除A,此时链表只剩B。接着调用SetTimerToFirst(),再次绑定1次Completed方法(现在总共绑定了2次),重启Timer等待B的时间。
  3. B的时间到了,Elapsed触发,此时会执行2次Completed方法:
    • 第一次执行:停止Timer,打印B的Id,移除B,链表为空,不再调用SetTimerToFirst()。
    • 第二次执行:此时链表已经为空,TimerItems.First是null,尝试访问TimerItems.First.Value.Id就会抛出NullReferenceException。
      这就是为什么你看到事件触发了3次(A触发1次 + B触发2次)。

解决方案

要解决这个问题,我们需要确保每次绑定事件前先移除旧的绑定,避免重复注册;同时在事件处理方法中增加空值检查,增强代码健壮性。

1. 修改SetTimerToFirst()方法,避免重复绑定事件

每次设置Timer前,先移除旧的Completed事件处理器,再添加新的:

private static void SetTimerToFirst()
{
    // 先移除旧的事件绑定,防止重复触发
    _timer.Elapsed -= Completed;
    _timer.Interval = TimerItems.First.Value.MilliSecondsCountToCompletion;
    _timer.AutoReset = false;
    _timer.Elapsed += Completed;
    _timer.Start();
}

2. 增强Completed()方法的空值检查

在访问链表头部前先判空,即使出现意外情况也不会崩溃:

private static void Completed(Object sender, EventArgs e)
{
    _timer.Stop();
    // 先检查链表头部是否存在,再执行后续操作
    if (TimerItems.First != null)
    {
        Console.WriteLine(TimerItems.First.Value.Id);
        TimerItems.RemoveFirst();
        if (TimerItems.First != null)
        {
            SetTimerToFirst();
        }
    }
}

3. 优化MilliSecondsCountToCompletion属性(可选但建议)

原代码中用Seconds > 0判断剩余时间的逻辑有问题,比如剩余0.5秒时,Seconds返回0,但实际还有500毫秒,这时候会错误返回1毫秒。修改为直接判断总毫秒数:

public double MilliSecondsCountToCompletion
{
    get
    {
        var timeRemaining = ResultTime.Subtract(DateTime.Now);
        return timeRemaining.TotalMilliseconds > 0 ? timeRemaining.TotalMilliseconds : 1;
    }
}

验证修改后的逻辑

修改后,每次SetTimerToFirst()只会绑定一次Completed方法:

  • 处理A时,执行1次Completed,移除A后绑定1次新的处理器等待B。
  • B的时间到了,只执行1次Completed,移除B后链表为空,不再重启Timer,不会出现额外的事件触发。

这样就能彻底解决事件多次触发和空引用异常的问题了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:53:23