To Show a timer for how Long a specific process runs I'm using a Background worker to update an execution time Label. Naturally this should be done every second so that the user sees that it increases consistently.
After trying around a bit and failing utterly I went down the road that I'm checking every 150 milliseconds if the next second is already there and then I update the Display.
private void ExecutionTimerBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch executionTime = new Stopwatch();
double lastUpdateAtSeconds = 0;
executionTime.Start();
while (!ExecutionTimerBackgroundWorker.CancellationPending)
{
Thread.Sleep(150); // Sleep for some while to give other threads time to do their stuff
if (executionTime.Elapsed.TotalSeconds > lastUpdateAtSeconds + 1) // Update the Execution time display only once per second
{
ExecutionTimerBackgroundWorker.ReportProgress(0, executionTime.Elapsed); // Update the Execution time Display
lastUpdateAtSeconds = executionTime.Elapsed.TotalSeconds;
}
}
executionTime.Stop();
}
private void ExecutionTimerBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the display to the execution time in Minutes:Seconds format
ExecutionTimeLabel.Text = ((TimeSpan) e.UserState).ToString(@"mm\:ss");
}
Now this seems to me a bit inefficient as I run it every 150 milliseconds to just look "hey has the next second already arrived or not". I also tried a different Approach already where I calculate how much time is needed until the next second but at that one I had a few instances where a jump by 2 instead of 1 second each in the Display happened.
So my question here is: Is there any more efficient way to do this? Or is that already how it should be done?
I have found that if you want to display changes every second then you should attempt to make the changes every tenth of a second for it to appear continuous for the user - maybe even more often than that.
Now I would avoid the use of a background worker entirely for this. Instead I'd use Microsoft's Reactive Framework (NuGet "Rx-Main" or "Rx-WinForms" in your case).
Here's the basic code for that:
var start = DateTimeOffset.Now;
var subscription =
Observable
.Interval(TimeSpan.FromSeconds(0.1))
.Select(x => DateTimeOffset.Now.Subtract(start).TotalSeconds)
.Select(x => (int)x)
.DistinctUntilChanged()
.ObserveOn(this)
.Subscribe(x => this.label1.Text = x.ToString());
This code creates a timer (.Interval(...)
) that fires every tenth of a second. It then computes the time in seconds since the code started, turns this into an integer, and drops all consecutive values that are the same. Finally it observes the observable on the UI thread (.ObserveOn(this)
) and then subscribes by assigning the value to (in my case) a label on my form - you could use whatever control type you liked.
To stop the subscription, just do this:
subscription.Dispose();
It will clean up everything properly.
The code should be quite readable event if you are not familiar with the Reactive Framework.
Now I've used DateTimeOffset
instead of Stopwatch
as you don't need high resolution timing for updates occurring every second. Nothing would stop you using a Stopwatch
if you wanted.
For that matter I would suggest using System.Windows.Forms.Timer
. With this timer you will not run into cross-thread issues when updating Label
text and it is very easy to use.
private Timer timer;
private int secondsElapsed;
private void InitTimer()
{
timer = new Timer();
timer.Interval = 1000; // milliseconds
timer.Tick += new EventHandler(timer_Tick);
}
void timer_Tick(object sender, EventArgs e)
{
secondsElapsed++;
lblSecondsElapsed.Text = secondsElapsed.ToString();
}
private void btnStart_Click(object sender, EventArgs e)
{
secondsElapsed = 0;
timer.Start();
}
private void btnAbort_Click(object sender, EventArgs e)
{
timer.Stop();
}
Edit:
Source: MSDN - Timer Class (System.Windows.Forms)
This timer is optimized for use in Windows Forms applications and must be used in a window.
Note
The Windows Forms Timer component is single-threaded, and is limited to an accuracy of 55 milliseconds. If you require a multithreaded timer with greater accuracy, use the Timer class in the System.Timers namespace.
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