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

如何确保System.Timers.Timer在Dispose时完全终止并清理消息队列?

如何在Dispose中确保System.Timers.Timer完全停止且无残留回调?

我想在Dispose方法中关闭System.Timers.Timer,必须100%确保它已经完全运行结束,不会有ElapsedEventHandler的残留回调。因为没法直接检查回调是否完成,我做了这些操作:设置RunTimer布尔值防止Dispose时定时器重启;用SemaphoreSlim阻塞定时器的Elapsed方法;设置两倍定时器间隔的延迟来确保最后一次循环完成。但不确定这种方法对不对,是不是只要停止定时器交给GC处理就行?下面是我的实现代码:

public class MessageWriter : IDisposable
{
    public delegate void WriteMessageDelegate(OutputMessage messageQueue);
    private ConcurrentQueue<OutputMessage> MessageQueue = new ConcurrentQueue<OutputMessage>();
    public WriteMessageDelegate OutputToDevice;

    private System.Timers.Timer WriteTimer { get; set; }
    public int TimerInterval { get; set; }
    private SemaphoreSlim WritingMessages;
    private bool RunTimer { get; set; }

    private bool _disposed = false;

    public MessageWriter(double timerInterval)
    {
        TimerInterval = TimerInterval;

        RunTimer = true;

        WritingMessages = new SemaphoreSlim(1);

        WriteTimer = new System.Timers.Timer();
        WriteTimer.AutoReset = false;
        WriteTimer.Interval = TimerInterval;
        WriteTimer.Elapsed += WriteTimerElapsed;
    }

    private void WriteTimerElapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            WritingMessages.Wait();

            EmptyQueue();

            if (RunTimer)
                WriteTimer.Start();
        }
        catch (Exception ex)
        {
            //Nothing we can do here, this is the logger
            Console.WriteLine("Exception with Log timer - : " + ex.Message);
        }
        finally
        {
            WritingMessages.Release();
        }
    }

    private void EmptyQueue()
    {
        while (MessageQueue.TryDequeue(out var message))
        {
            OutputToDevice?.Invoke(message);
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
        }

        //Prevent timer running again
        RunTimer = false;

        //Disable timer if its running
        if (WriteTimer != null)
        {
            WriteTimer.Enabled = false;
            WriteTimer.Stop();
            WriteTimer.Dispose();
        }

        //Wait for double the timer interval
        System.Threading.Thread.Sleep(TimerInterval + TimerInterval);

        //Wait until this message writer has finished
        WritingMessages.Wait();

        //Release the semaphore so the timer can run again if it needs to
        WritingMessages.Release();

        //Get rid of any messages left in the queue
        EmptyQueue();

        //Dispose of the delegate
        OutputToDevice = null;

        // Free any unmanaged objects here.
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~MessageWriter()
    {
        Dispose(false);
    }
}

你的实现存在的问题

  1. 固定延迟等待完全没必要:Thread.Sleep(2*TimerInterval)是不可靠的——如果回调执行时间超过间隔,等待时间不够;如果回调早已完成,就是纯粹浪费时间。
  2. Semaphore操作逻辑错误:Dispose中调用WritingMessages.Wait()后又Release(),会导致信号量计数异常,若后续有意外回调触发,可能出现多线程同时执行EmptyQueue的情况。
  3. 终结器路径风险:在~MessageWriter()中调用Dispose(false)时,WritingMessages可能已被GC回收,调用其方法会抛出空引用异常;且终结器线程不能阻塞,调用Wait()会引发性能问题甚至死锁。
  4. 队列清空时机不安全:在释放信号量后调用EmptyQueue,此时若有其他线程操作队列,会存在线程安全问题,且OutputToDevice已被设为null,调用无意义。

正确实现思路

核心是阻止新回调触发+等待正在执行的回调完成,无需依赖固定等待时间,调整方案如下:

  1. 用Stop()终止定时器,阻止新的Elapsed事件触发,但已启动的回调会继续执行。
  2. 借助SemaphoreSlim等待回调执行完毕,确保无残留操作。
  3. 区分Dispose(true)(显式调用)和Dispose(false)(终结器)路径,仅在显式调用时处理托管资源,避免终结器线程阻塞。
  4. 在确认无回调执行后,安全清空队列并清理委托。

修正后的代码

public class MessageWriter : IDisposable
{
    public delegate void WriteMessageDelegate(OutputMessage message);
    private readonly ConcurrentQueue<OutputMessage> _messageQueue = new ConcurrentQueue<OutputMessage>();
    public WriteMessageDelegate OutputToDevice;

    private readonly System.Timers.Timer _writeTimer;
    public int TimerInterval { get; }
    private readonly SemaphoreSlim _writingSemaphore = new SemaphoreSlim(1, 1);
    private volatile bool _runTimer = true;

    private bool _disposed = false;

    public MessageWriter(double timerInterval)
    {
        TimerInterval = (int)timerInterval;

        _writeTimer = new System.Timers.Timer(timerInterval)
        {
            AutoReset = false
        };
        _writeTimer.Elapsed += WriteTimerElapsed;
        _writeTimer.Start();
    }

    private void WriteTimerElapsed(object sender, ElapsedEventArgs e)
    {
        if (!_runTimer) return;

        try
        {
            _writingSemaphore.Wait();
            EmptyQueue();
        }
        catch (Exception ex)
        {
            Console.WriteLine("日志定时器异常: " + ex.Message);
        }
        finally
        {
            _writingSemaphore.Release();
        }

        if (_runTimer)
        {
            _writeTimer.Start();
        }
    }

    private void EmptyQueue()
    {
        while (_messageQueue.TryDequeue(out var message))
        {
            OutputToDevice?.Invoke(message);
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        _runTimer = false;

        if (disposing)
        {
            _writeTimer?.Stop();
            _writeTimer?.Dispose();

            _writingSemaphore.Wait();
            try
            {
                EmptyQueue();
                OutputToDevice = null;
            }
            finally
            {
                _writingSemaphore.Release();
                _writingSemaphore.Dispose();
            }
        }

        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~MessageWriter()
    {
        Dispose(false);
    }
}

关键改进说明

  • volatile修饰_runTimer:确保多线程下布尔值的可见性,避免Dispose设置后,回调线程仍读取旧值。
  • 移除无用的Thread.Sleep:用SemaphoreSlim.Wait()可靠等待回调完成,只有当所有回调执行完毕(调用Release()后),Wait()才会返回。
  • 托管资源仅在显式Dispose时处理:终结器中不操作SemaphoreSlim和定时器,避免异常。
  • 回调前置状态检查:在回调开头判断_runTimer,避免Dispose后执行无效逻辑。

内容的提问来源于stack exchange,提问作者Tony Cheetham

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 04:10:10