Are there better ways to update the execution timer every second than using a background worker who checks every 150 ms?

To show a timer about how long a particular process takes, I use a background worker to update the runtime label. Naturally, this must be done every second so that the user sees that he is constantly increasing.

After several attempts and unsuccessful attempts, I went along the road that I check 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 a little ineffective to me, as I run it every 150 milliseconds just to see โ€œhey, that the next second has arrived or not.โ€ I also tried a different approach, when I calculate how much time it will take until the next second, but in this case I had several cases when there was a transition to 2 instead of 1 second on the display.

So my question is: is there a more efficient way to do this? Or is it already how to do it?

+6
source share
4 answers

I found that if you want to display changes every second, you should try to make changes every tenth of a second so that it seems continuous to the user - perhaps even more often than that.

Now I would completely avoid using a background worker. Instead, I would use the Microsoft Reactive Framework (NuGet "Rx-Main" or "Rx-WinForms" in your case).

Here is the basic code for this:

 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. Then it calculates the time in seconds since the code was run, turns it into an integer, and discards all consecutive values โ€‹โ€‹that are the same. Finally, it observes the observable in the user interface stream ( .ObserveOn(this) ), and then subscribes by assigning a value (in my case) to the label in my form - you can use any type of control that you like.

To stop the subscription, simply do the following:

 subscription.Dispose(); 

He will clean everything right.

The code should be readable if you are not familiar with the Reactive Framework.

Now I used DateTimeOffset instead of Stopwatch , as you do not need high-resolution synchronization for updates occurring every second. Nothing would stop you using Stopwatch if you wanted to.

+2
source

In this regard, I would suggest using System.Windows.Forms.Timer . With this timer, you will not encounter cross-thread problems 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 should be used in a window.

Note

The Timer Windows Forms component is single-threaded and limited to 55 milliseconds precision. If you need a multi-threaded timer with greater precision, use the Timer class in the System.Timers namespace.

+1
source

You might want to take a look at System.Threading.Timer or System.Timers.Timer , but to be honest, even if you set the interval to 1 second, both of them are not very accurate: / most of the time I go from 990 ms, or I I use the stream the same way you use BackgroundWorker (and I donโ€™t think that these timers really work differently).

Editing: Funny enough, I just looked in the .NET Framework and Timers.Timer internally uses Threading.Timer.

0
source

You are completely right in your conclusions regarding the necessary sampling interval for a smooth change in the displayed second. The human observer knows when the next second will come, and therefore, he will notice even a slight skew between this and the moment when it is actually displayed . So you really need to make your check above level below level two.

All performance considerations should be centered around the issue of whether your check is expensive or not. In the above example, the time spent on the if-sentence is negligible, so the thread will still sleep again. In this way, you can safely let small sleeping intervals โ€œjustโ€ to make the display white.

If, however, your check is expensive, you can choose adaptive sleep: sleep long if the next second is far, short when it is soon ...

Please remember that, as Anton Kedrov stated, updating GUI components from anything other than a GUI thread causes deprivation. Depending on the level of artificiality of your example, you should either select a completely timer-based solution, or insert the System.Windows.Forms.Timer decoupling to dig the calculated values โ€‹โ€‹(through thread-safe fields, please) into the graphical user interface.

0
source

Source: https://habr.com/ru/post/988306/


All Articles