C# System.Threading.Timer 定时任务阻塞及被 GC 回收问题的解决

远山来客
远山来客
发布时间:2026-10-16 | 4人浏览过
原创

在C#开发中,使用 System.Threading.Timer 实现定时任务时,开发者常遇到任务突然停止(表现为不再执行代码且不打印日志)。这一现象通常由两个核心机制引起:一是局部定义的 Timer 对象被垃圾回收器(GC)意外回收;二是回调方法执行耗时较长,导致线程池中的后续任务排队阻塞,甚至出现多个线程同时执行回调的并发混乱。本文将分析这些问题的成因,并提供标准的修复方案。

基础用法与潜在缺陷

System.Threading.Timer 是 .NET 中轻量级的定时调度工具,它基于线程池工作。其核心构造函数参数如下:

Timer(TimerCallback callback, object state, int dueTime, int period)
  • callback:执行的回调委托。
  • state:传递给回调的参数对象。
  • dueTime:首次执行前的延时(毫秒)。
  • period:两次执行之间的间隔(毫秒)。

如果在方法内部定义 Timer,如以下示例所示:

protected internal void PollClient()
{
    int i=0;
    Timer t = new Timer(p => {
        i++;
        // ... 业务逻辑 ...
    }, null, 0, 1000);
}

存在两个主要风险:

  1. 被 GC 回收:局部变量 t 在执行完方法后失去引用,如果未被其他强引用持有,GC 可能会立即将其清理,导致定时任务终止。
  2. 线程阻塞与并发:Timer 本身仅负责触发回调,不保证回调的执行时间。如果回调体内的业务逻辑耗时超过 period 设定的间隔,计时器会在上一个回调未完成时再次触发回调,导致多个线程同时执行逻辑,引发竞态条件或资源耗尽。

修正方案一:防止被 GC 回收

最简单的防回收手段是将 Timer 实例提升为类的 static 字段或全局变量。这样对象的生命周期将绑定到类级别,避免被提前销毁。

static Timer _timer = null;

修正方案二:串行化执行并避免线程浪费

为了确保任务不会因执行时间长而堆积在内存中,推荐采用“一次性触发 + 循环内手动重置”的策略。即设置 period 为 Timeout.Infinite,并在每次回调的 finally 块中通过 _timer.Change() 设定下一次触发的时间。

public class TaskScheduler
{
    static int i = 0;
    static Timer _timer = null;
    private readonly ILogger logger = /* Logger instance */;

    protected void InitializePollClient()
    {
        // dueTime: 首次延迟 1000ms; period: 无限(单次执行)
        _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
    }

    private void TimerCallback(object state)
    {
        try
        {
            i++;
            // 此处执行业务逻辑
            var deviceContext = GetDeviceContext("123456789");
            SendMessage(messageList[i % 7], deviceContext.tcpSession.writerContext);
            logger.Info("客户端数量:" + deviceContextList.Count + " 循环次数:" + i);
        }
        catch (Exception e)
        {
            logger.Error("定时下发报文异常:" + e);
        }
        finally
        {
            // 关键步骤:在下一次触发前,重新设置延时
            // 这确保了线程在完成当前工作后被释放回线程池,不被长时间占用
            if (_timer != null)
                _timer.Change(1000, Timeout.Infinite);
        }
    }
}

原理解析与效果验证

经过上述重构后,定时任务的运行行为将符合预期:

1. **稳定性**:因为 _timer 是静态字段,不会被局部作用域结束所影响,彻底杜绝了“静默失效”的问题。

2. **线程健康**:通过将 period 设为无穷大,只有当代码主动调用 Change 时才会唤醒下一次回调。这使得线程可以在任务完成后立即返回线程池,避免了在高负载场景下创建大量等待执行的线程任务,从而消除了队列阻塞和上下文切换带来的性能损耗。

以上就是 C# 中 System.Threading.Timer 定时任务被阻塞或被 GC 回收问题的详细内容,更多关于 C# 定时任务优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表