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 event
In System.Timers.Timer the Elapsed event is added to the ThreadPool when the class is created. After that it is fired. The Enabled property can be false at that time. You can't do anything about that, but what you can do is test if the Enabled property is true when the Elapsed event fires. I do override the Enabled property to make this magic happening, as extra I also put an IsDisposed property in it:
public class DisposableTimer : System.Timers.Timer {
///
/// override the Timer base class Enabled property
///
///
/// the code in the Elapsed event should only be executed when the Enabled property is set to "true".
/// we cannot prevent that the Elapsed event is fired at the start, because its automatically put in the ThreadPool,
/// but we can prevent that the code in it can be executed when the Enabled property is "false".
///
private bool enabled;
public new bool Enabled
{
get
{
return enabled;
}
set
{
enabled = base.Enabled = value;
}
}
///
/// count the heartbeats
///
public int HeartbeatCounter { get; set; }
///
/// name of timer
///
public string TimerName { get; set; }
///
/// show heartbeat on console
///
public bool ShowHeartBeat { get; set; }
// type of entry in eventlog
public EventLogEntryType EventLogEntryType { get; set; }
// updated interval to process messages
public Func UpdatebleInterval { get; set; }
///
/// used eventlog
///
public EventLog EventLog { get; set; }
///
/// message logging
///
///
/// this property needs to be dynamic because in
/// pda service a different class is used but luckily :-)
/// with the same method for adding loggings.
///
public dynamic MessageLogging { get; set; }
///
/// make sure there are no overlapping set of timer callbacks
///
private object locker;
///
/// initialize timer class
///
/// action to perform
/// name of timer
public DisposableTimer(List actions, string timerName) : base()
{
// used to make sure there are no overlapping executing sets of timer callbacks
locker = new object();
// wrapper for the actions that need to be performed.
base.Elapsed += (s, a) => Callback(actions);
// set name of timer
this.TimerName = timerName;
/*
* only once a callback is executed after elapsed time,
* because there is only one callback executed there can be
* no overlap, because the "reset" is done after the set of
* callbacks are executed.
*/
AutoReset = false;
// timer is not started yet
Enabled = false;
}
///
/// check if verwijder bericht timer is disposed
///
public bool IsDisposed
{
get
{
var timerType = typeof(System.Timers.Timer);
var timerDisposedField = timerType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
return (bool)timerDisposedField.GetValue(this);
}
}
///
/// after a callback a timer needs to be reset to continue running if AutoReset=false.
///
/// new interval of timer
private void Reset(double interval)
{
// stop the timer
Stop();
// only do when not disposed yet.
if (!IsDisposed)
{
// adjust interval if needed
if (interval != 0)
Interval = interval;
// release exclusive lock
Monitor.Exit(locker);
}
// start the timer again
Start();
}
///
/// only start if not disposed and started yet
///
public new void Start()
{
if (!IsDisposed && !Enabled)
Enabled = true;
}
///
/// only stop if not disposed and stopped yet
///
public new void Stop()
{
if (!IsDisposed && Enabled)
Enabled = false;
}
///
/// set of callbacks to perform after timer elapse interval
///
/// list of callbacks inside this wrapper to execute
public void Callback(List callBackActions)
{
// only execute callbacks if timer is enabled.
if (Enabled)
{
/*
* AutoReset = false, so a callback is only executed once,
* because of this overlapping callbacks should not occur,
* but to be sure exclusive locking is also used.
*/
var hasLock = false;
// show heartbeat at output window
if (ShowHeartBeat)
Debug.WriteLine(string.Format("HeartBeat interval: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId() ));
// execute callback action.
try
{
// only perform set of actions if not executing already on this thread.
Monitor.TryEnter(locker, ref hasLock);
if (hasLock)
{
// show heartbeat at output window
if (ShowHeartBeat)
Debug.WriteLine(string.Format(" action: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));
// execute the set of callback actions
foreach (Action callBackAction in callBackActions)
{
// execute callback
try
{
callBackAction();
}
// log error, but keep the action loop going.
catch (Exception ex)
{
EventLog.WriteEntry(ex.Message, EventLogEntryType.Warning);
MessageLogging.Insert(ex.Message);
}
}
}
// show that action is busy
else if (ShowHeartBeat)
Debug.WriteLine(string.Format(" busy: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));
}
// adjust interval when needed and release exclusive lock when done.
finally
{
// after the complete action is finished the lock should be released.
if (hasLock)
{
// timer interval can be changed when timer is active, callback function is needed for this.
double newInterval = 0;
if (UpdatebleInterval != null)
{
// calculate new interval for timer
double updatedInterval = UpdatebleInterval();
if (Interval != updatedInterval)
{
// based on Dutch
var dutchCultureInfo = new CultureInfo("nl-NL", false).TextInfo;
// write interval change to loggings
string intervalMessage = dutchCultureInfo.ToTitleCase(string.Format(@"{0} interval veranderd van {1} naar {2} seconden", TimerName, Interval / 1000, updatedInterval / 1000));
EventLog.WriteEntry(intervalMessage, EventLogEntryType.Information);
MessageLogging.Insert(intervalMessage);
// set for new interval
newInterval = updatedInterval;
}
}
// make ready for new callback after elapsed time, lock can be released by now.
Reset(newInterval);
}
}
}
// show heartbeat at output window
else if (ShowHeartBeat)
Debug.WriteLine(string.Format("HeartBeat thread: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId()));
}
}