我为此搜寻了许多解决方案。我正在寻找一种简单干净的方法来防止在停止后调用System.Threading.Timer的回调方法。
我似乎找不到任何方法,这有时会导致我使用可怕的线程-线程.sleep-线程.abort组合。
是否可以使用锁定(lock)来实现?
我为此搜寻了许多解决方案。我正在寻找一种简单干净的方法来防止在停止后调用System.Threading.Timer的回调方法。
我似乎找不到任何方法,这有时会导致我使用可怕的线程-线程.sleep-线程.abort组合。
是否可以使用锁定(lock)来实现?
Timer
设置为永不恢复;Timer.Change 方法可以使用 dueTime
和 period
的值,指示定时器永不重新启动:this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
虽然转而使用System.Timers.Timer
可能是一个“更好”的解决方案,但总会有些时候不太实际;只使用Timeout.Infinite
就足够了。
uint
,基本上是uint.MaxValue
(尽管他们没有明确使用它,而是使用了(uint)-1
)。Timeout
类说:“用于需要超时(Object.Wait、Thread.Sleep等)的方法的常量,表示不应发生超时。”计时器类根据此值显式禁用计时器。 - Dave Cousineau像Conrad Frix建议的一样,你应该使用System.Timers.Timer
类,例如:
private System.Timers.Timer _timer = new System.Timers.Timer();
private volatile bool _requestStop = false;
public constructor()
{
_timer.Interval = 100;
_timer.Elapsed += OnTimerElapsed;
_timer.AutoReset = false;
_timer.Start();
}
private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// do work....
if (!_requestStop)
{
_timer.Start();//restart the timer
}
}
private void Stop()
{
_requestStop = true;
_timer.Stop();
}
private void Start()
{
_requestStop = false;
_timer.Start();
}
对于System.Threading.Timer,可以进行以下操作(还可以保护回调方法不在处理已释放的定时器-ObjectDisposedException):
class TimerHelper : IDisposable
{
private System.Threading.Timer _timer;
private readonly object _threadLock = new object();
public event Action<Timer,object> TimerEvent;
public void Start(TimeSpan timerInterval, bool triggerAtStart = false,
object state = null)
{
Stop();
_timer = new System.Threading.Timer(Timer_Elapsed, state,
System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
if (triggerAtStart)
{
_timer.Change(TimeSpan.FromTicks(0), timerInterval);
}
else
{
_timer.Change(timerInterval, timerInterval);
}
}
public void Stop(TimeSpan timeout = TimeSpan.FromMinutes(2))
{
// Wait for timer queue to be emptied, before we continue
// (Timer threads should have left the callback method given)
// - http://woowaabob.blogspot.dk/2010/05/properly-disposing-systemthreadingtimer.html
// - http://blogs.msdn.com/b/danielvl/archive/2011/02/18/disposing-system-threading-timer.aspx
lock (_threadLock)
{
if (_timer != null)
{
ManualResetEvent waitHandle = new ManualResetEvent(false)
if (_timer.Dispose(waitHandle))
{
// Timer has not been disposed by someone else
if (!waitHandle.WaitOne(timeout))
throw new TimeoutException("Timeout waiting for timer to stop");
}
waitHandle.Close(); // Only close if Dispose has completed succesful
_timer = null;
}
}
}
public void Dispose()
{
Stop();
TimerEvent = null;
}
void Timer_Elapsed(object state)
{
// Ensure that we don't have multiple timers active at the same time
// - Also prevents ObjectDisposedException when using Timer-object
// inside this method
// - Maybe consider to use _timer.Change(interval, Timeout.Infinite)
// (AutoReset = false)
if (Monitor.TryEnter(_threadLock))
{
try
{
if (_timer==null)
return;
Action<Timer, object> timerEvent = TimerEvent;
if (timerEvent != null)
{
timerEvent(_timer, state);
}
}
finally
{
Monitor.Exit(_threadLock);
}
}
}
}
void StartTimer()
{
TimerHelper _timerHelper = new TimerHelper();
_timerHelper.TimerEvent += (timer,state) => Timer_Elapsed();
_timerHelper.Start(TimeSpan.FromSeconds(5));
System.Threading.Sleep(TimeSpan.FromSeconds(12));
_timerHelper.Stop();
}
void Timer_Elapsed()
{
// Do what you want to do
}
就我们而言,我们经常使用这种模式:
// set up timer
Timer timer = new Timer(...);
...
// stop timer
timer.Dispose();
timer = null;
...
// timer callback
{
if (timer != null)
{
..
}
}
我在网上读了很多关于如何同步处理 System.Threading.Timer 销毁的无聊文章。这就是为什么我发布这篇文章,以尝试在某种程度上纠正这种情况。如果我所写的内容有误,请随时指出并批评我 ;-)
我认为存在以下陷阱:
Timer.Dispose(WaitHandle)
可能返回 false。如果已经被销毁,则会这样做(我不得不查看源代码)。在这种情况下,它 不会 设置 WaitHandle
- 因此不要等待它!WaitHandle
超时。说真的-如果您对超时不感兴趣,那么您在等待什么?Timer.Dispose(WaitHandle)
与-Slim waithandles 不兼容,或者不像人们期望的那样工作。例如,以下代码 不会 正常工作(它永远阻塞): using(var manualResetEventSlim = new ManualResetEventSlim)
{
timer.Dispose(manualResetEventSlim.WaitHandle);
manualResetEventSlim.Wait();
}
标题有点过于“大胆”了,但以下是我处理问题的尝试——一个包装器,处理双重释放、超时和ObjectDisposedException
。虽然它没有提供Timer
的所有方法,但可以自由地添加它们。
internal class Timer
{
private readonly TimeSpan _disposalTimeout;
private readonly System.Threading.Timer _timer;
private bool _disposeEnded;
public Timer(TimeSpan disposalTimeout)
{
_disposalTimeout = disposalTimeout;
_timer = new System.Threading.Timer(HandleTimerElapsed);
}
public event Action Elapsed;
public void TriggerOnceIn(TimeSpan time)
{
try
{
_timer.Change(time, Timeout.InfiniteTimeSpan);
}
catch (ObjectDisposedException)
{
// race condition with Dispose can cause trigger to be called when underlying
// timer is being disposed - and a change will fail in this case.
// see
// https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
if (_disposeEnded)
{
// we still want to throw the exception in case someone really tries
// to change the timer after disposal has finished
// of course there's a slight race condition here where we might not
// throw even though disposal is already done.
// since the offending code would most likely already be "failing"
// unreliably i personally can live with increasing the
// "unreliable failure" time-window slightly
throw;
}
}
}
private void HandleTimerElapsed(object state)
{
Elapsed?.Invoke();
}
public void Dispose()
{
var waitHandle = new ManualResetEvent(false));
// returns false on second dispose
if (_timer.Dispose(waitHandle))
{
if (waitHandle.WaitOne(_disposalTimeout))
{
_disposeEnded = true;
waitHandle.Dispose();
}
else
{
// don't dispose the wait handle, because the timer might still use it.
// Disposing it might cause an ObjectDisposedException on
// the timer thread - whereas not disposing it will
// result in the GC cleaning up the resources later
throw new TimeoutException(
"Timeout waiting for timer to stop. (...)");
}
}
}
}
using
的好处是什么?假设 WaitOne(x)
失败并抛出 ArgumentOutOfRangeException
,会发生什么?我认为在这种情况下 ManualResetEvent
不会被处理。
根据文档,ManualResetEvent.Dispose()
在任何情况下都不会抛出异常,因此使用 using
隐藏另一个异常也不应该有问题。 - BatteryBackupUnitWaitOne
返回 true 时才进行处理会更有益。 - BatteryBackupUnitTriggerOnceIn()
。它的目的是在事件运行时停止计时器吗? - Mike BrunoTimer
包装类示例不是一个重复定时器,而是每次调用TriggerOnceIn
时,Timer.Elapsed
事件将在您传递的TimeSpan time
之后被触发。
如果在Timer.Elapsed
事件之前多次调用TriggerOnceIn
,则Timer.Elapsed
事件应该仅发生一次(在指定的最晚时间)。请注意,由于并发性质,这不能保证。此外,Timer.Dispose()
应该取消Elapsed
的发生,但当然它可能会与同时发生。 - BatteryBackupUnit这就是为什么在事件处理程序中提供一些逻辑更加安全。也许是一些ManualResetEvent,一旦您不再需要事件调用,它就会被重置。因此,您Dispose计时器,然后设置ManualResetEvent。并且在计时器事件处理程序中,第一件事就是测试ManualResetEvent。如果它处于重置状态 - 立即返回。因此,您可以有效地防止某些代码的意外执行。
dispose
即可。这将停止计时器并防止未来预定的调用。class Program
{
static void Main(string[] args)
{
WriteOneEverySecond w = new WriteOneEverySecond();
w.ScheduleInBackground();
Console.ReadKey();
w.StopTimer();
Console.ReadKey();
}
}
class WriteOneEverySecond
{
private Timer myTimer;
public void StopTimer()
{
myTimer.Dispose();
myTimer = null;
}
public void ScheduleInBackground()
{
myTimer = new Timer(RunJob, null, 1000, 1000);
}
public void RunJob(object state)
{
Console.WriteLine("Timer Fired at: " + DateTime.Now);
}
}
public class InvalidWaitHandle : WaitHandle
{
public IntPtr Handle
{
get { return InvalidHandle; }
set { throw new InvalidOperationException(); }
}
}
实例化计时器:
_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);
然后在回调方法内部:
if (_secondsElapsed > 80)
{
_t.Dispose(new InvalidWaitHandle());
}