Apologies in advance for rather long post and lot of code.
My application has a timed autosave feature. Users asked that I provide a visual indicator of how much time is left. I did some research on count down timers and eventually wrote the class below:
public class CountDownTimer
{
private Timer timer;
private int remaining;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Count down ticked delegate. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
///
/// <param name="remaining"> The remaining. </param>
/// <param name="maximum"> The maximum. </param>
////////////////////////////////////////////////////////////////////////////////////////////////////
public delegate void CountDownTickedDelegate(int remaining, int maximum);
/// <summary> Event queue for all listeners interested in Ticked event. </summary>
public event CountDownTickedDelegate Ticked;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Count down percent delegate. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
///
/// <param name="percent"> The percent. </param>
////////////////////////////////////////////////////////////////////////////////////////////////////
public delegate void CountDownPercentDelegate(int percent);
/// <summary> Event queue for all listeners interested in Percent events. </summary>
public event CountDownPercentDelegate Percent;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Count down done delegate. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public delegate void CountDownDoneDelegate();
/// <summary> Event queue for all listeners interested in Done events. </summary>
public event CountDownDoneDelegate Done;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Gets or sets the maximum value to count down from </summary>
///
/// <value> The maximum value. </value>
////////////////////////////////////////////////////////////////////////////////////////////////////
public int Maximum
{
get;
set;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Gets or sets a value indicating whether the timer is Paused. </summary>
///
/// <value> true if paused, false if not. </value>
////////////////////////////////////////////////////////////////////////////////////////////////////
public bool Paused
{
get;
set;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Starts this CountDownTimer. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public void Start()
{
timer = new Timer {
Interval = 1000
};
timer.Tick += onTimerTick;
timer.Enabled = true;
remaining = Maximum;
Paused = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Stops this CountDownTimer. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public void Stop()
{
if (timer == null)
{
return;
}
Paused = true;
timer.Enabled = false;
timer = null;
if (Percent != null)
{
Percent(100);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Resets and restarts this CountDownTimer. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public void Reset()
{
Stop();
Start();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Handles the timer tick event. </summary>
///
/// <remarks> Jon, 18/06/2012. </remarks>
///
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> Event information to send to registered event handlers. </param>
////////////////////////////////////////////////////////////////////////////////////////////////////
private void onTimerTick(object sender, EventArgs e)
{
if (remaining > 0)
{
if (Ticked != null)
{
Ticked(remaining, Maximum);
}
if (Percent != null)
{
int percent = remaining * 100 / Maximum;
Percent(percent);
}
if (!Paused)
{
remaining--;
}
else
{
remaining = Maximum;
}
}
else
{
Stop();
if (Done != null)
{
Done();
}
}
}
}
I am using a Timer and each time it ‘fires’ I decrement a counter. Each decrement kicks off an event so that my Form can present it visually. When the counter reaches zero another event kicks off the autosave.
There are a few other bits included to allow the autosave to be restarted if the user manually saves or if they open a new project.
It seemed to work for me. However a user is reporting that the longer the timer runs the shorter the interval between autosaves. I set the timer to tick every second and my investigations show that it runs at twice the speed. So if the counter is set to 60 (seconds) then it runs down every 30. I cannot replicate the behavior seen by the user but his log certainly shows things running much too fast.
This is in the same thread as the main app – is that likely to be an issue. All my tests so far have not turned anything up other than the tick seems to fire twice in a row every third time or so.
Many thanks in advance for any insight.
One problem I see, is that if
CountDownTimer.Start()is called two (or even multiple) times, without the appropriateCountDownTimer.Stop()calls, you end up with two or more activated instances of yourTimerobject, both invoking youronTimerTick()event handler.This could cause your described effect, as all running
Timerinstances decrease the remaining iterations separately.Is that possible with your calling code?
EDIT:
As a workaround I would suggest, you call
Stop()from withinStart(). Or even better, you do not recreate theTimerobject for every new count down action. Create theTimerobject in the constructor and only manipulate its properties.It is also not a bad idea to remove the
onTimerTick()event handler from thetimerinstance when you dispose theTimerobject. Otherwise the GC can not collect thetimerinstance as it still holds a reference to itsCountDownTimerinstance.