I have an Elapsed method in which I have a while loop. If the timer is disabled/stopped from another thread, I would like this loop to stop. Can I rely on the timer’s Enabled property in the Elapsed method for this or should I create a “volatile bool timerEnabled” variable just to be sure. My testings show that it’s OK, but I’d like to be sure of this before putting it in production.
This is what I’m trying to achieve (not actual code but close)
private volatile bool isElapsedAlreadyRunning
void myTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (!isElapsedAlreadyRunning) // to prevent reentrance
{
isElapsedAlreadyRunning = true;
try
{
while (myTimer.Enabled && some other condition)
{
do stuff
}
}
finally
{
isElapsedAlreadyRunning = false;
}
}
}
myTimer.Start() and myTimer.Stop() are in other methods that can be called frrom other threads
I’m using the System.Timers.Timer class
If you have any other comment or see any pitfall in this design feel free to comment 🙂
Thanks
Edit :
Man, threading is hard. Based on the answers and other stackoverflow questions (this answer particularly) this would be the way to do it (I hope this time it’s OK)
public class NoLockTimer : IDisposable
{
private readonly System.Timers.Timer _timer;
private bool _isTimerStopped = false;
private readonly object _isTimerStoppedLock = new object();
public NoLockTimer()
{
_timer = new System.Timers.Timer { AutoReset = false, Interval = 1000 };
_timer.Elapsed += delegate
{
try
{
while (!IsTimerStopped && some other condition)
{
// do stuff
}
}
catch (Exception e)
{
// Do some logging
}
finally
{
if (!IsTimerStopped)
{
_timer.Start(); // <- Manual restart.
}
}
};
_timer.Start();
}
public void Stop()
{
IsTimerStopped = true;
if (_timer != null)
{
_timer.Stop();
}
}
private bool IsTimerStopped
{
get
{
lock (_isTimerStoppedLock)
{
return _isTimerStopped;
}
}
set
{
lock (_isTimerStoppedLock)
{
_isTimerStopped = value;
}
}
}
public void Dispose()
{
Stop();
if (_timer != null)
{
_timer.Dispose();
}
}
}
No, this is not safe. The Elapsed event handler is called on a threadpool thread. You cannot predict when that thread actually calls your method, it depends on what other TP threads are running in the process. Having two calls in flight at the same time is technically possible. Which makes the volatile keyword on the isElapsedAlreadyRunning variable not nearly good enough to ensure that the method is thread-safe, you must use the lock keyword or Monitor.TryEnter() instead.
This problem disappears when you set the Timer’s AutoReset property to false. Be sure to restart the timer in a finally block, another nasty problem with the Timer.Elapsed event is that exceptions get swallowed without diagnostic. System.Threading.Timer is an all-around better timer with fewer surprises like this.
The Timer.Enabled property has a similar problem, you’ll always see it late.