如何确保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); } }
你的实现存在的问题
- 固定延迟等待完全没必要:
Thread.Sleep(2*TimerInterval)是不可靠的——如果回调执行时间超过间隔,等待时间不够;如果回调早已完成,就是纯粹浪费时间。 - Semaphore操作逻辑错误:Dispose中调用
WritingMessages.Wait()后又Release(),会导致信号量计数异常,若后续有意外回调触发,可能出现多线程同时执行EmptyQueue的情况。 - 终结器路径风险:在
~MessageWriter()中调用Dispose(false)时,WritingMessages可能已被GC回收,调用其方法会抛出空引用异常;且终结器线程不能阻塞,调用Wait()会引发性能问题甚至死锁。 - 队列清空时机不安全:在释放信号量后调用
EmptyQueue,此时若有其他线程操作队列,会存在线程安全问题,且OutputToDevice已被设为null,调用无意义。
正确实现思路
核心是阻止新回调触发+等待正在执行的回调完成,无需依赖固定等待时间,调整方案如下:
- 用
Stop()终止定时器,阻止新的Elapsed事件触发,但已启动的回调会继续执行。 - 借助
SemaphoreSlim等待回调执行完毕,确保无残留操作。 - 区分
Dispose(true)(显式调用)和Dispose(false)(终结器)路径,仅在显式调用时处理托管资源,避免终结器线程阻塞。 - 在确认无回调执行后,安全清空队列并清理委托。
修正后的代码
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
相关产品推荐
相关产品推荐

