I have attempted to create a derived class of Timer that allows for a 'Pause' latch to be set to keep the worker thread from reactivating the timer. However, Elapsed events are continued to be raised when AutoReset is set to false and the Enabled accessor appears to be doing it's job in preventing the Enabled property of the base class from being modified once the Paused variable is set.
Why is this happening or what strategies should I use to further understand what interactions are actually happening here?
I have attached the implementation of the derived class below.
using System.Timers
class PauseableTimer : Timer
{
public bool Paused;
new public bool Enabled
{
get
{
return base.Enabled;
}
set
{
if (Paused)
{
if (!value) base.Enabled = false;
}
else
{
base.Enabled = value;
}
}
}
}
Example illustrating problem.
class Program
{
private static PauseableTimer _pauseableTimer;
private static int _elapsedCount;
static void Main(string[] args)
{
_pauseableTimer = new PauseableTimer(){AutoReset = false,Enabled = false,Paused = false};
_pauseableTimer.Elapsed += pauseableTimer_Elapsed;
_pauseableTimer.Interval = 1;
_pauseableTimer.Enabled = true;
while(_elapsedCount<100)
{
if (_elapsedCount > 50) _pauseableTimer.Paused = true;
}
}
static void pauseableTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine(String.Format("this.Enabled:'{0}',Paused:'{1}',AutoReset:'{2}",_pauseableTimer.Enabled,_pauseableTimer.Paused,_pauseableTimer.AutoReset));
_elapsedCount++;
_pauseableTimer.Interval = _pauseableTimer.Interval == 1 ? 2 : 1; //This line breaks it.
_pauseableTimer.Enabled = true;
}
}
Relevant document, System.Timers.Timer.Interval
Note If Enabled and AutoReset are both set to false, and the timer has previously been enabled, setting the Interval property causes the Elapsed event to be raised once, as if the Enabled property had been set to true. To set the interval without raising the event, you can temporarily set the AutoReset property to true.
The recommended solution of setting AutoReset
to true does not solve the problem because there is an undocumented behavior of setting AutoReset
to true during an event handler also allowing for an event to be fired.
The solution seems to be to build out the derived object to the point where you can keep any of the apparently many ways that an event can be fired again from happening.
Below is the implementation that I ended with.
public class PauseableTimer : Timer
{
private bool _paused;
public bool Paused
{
get { return _paused; }
set
{
Interval = _interval;
_paused = value;
}
}
new public bool Enabled
{
get
{
return base.Enabled;
}
set
{
if (Paused)
{
if (!value) base.Enabled = false;
}
else
{
base.Enabled = value;
}
}
}
private double _interval;
new public double Interval
{
get { return base.Interval; }
set
{
_interval = value;
if (Paused){return;}
if (value>0){base.Interval = _interval;}
}
}
public PauseableTimer():base(1){}
public PauseableTimer(double interval):base(interval){}
}
Everything is more complex in multithreading, I'm afraid. Assuming your code is working as you wish, there is a window where in-flight events can get raised after you reset the Enabled
property. See this quote from the MSDN docs.
The signal to raise the Elapsed event is always queued for execution on a ThreadPool thread. This might result in the Elapsed event being raised after the Enabled property is set to false. The code example for the Stop method shows one way to work around this race condition.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With