Friday, March 28, 2014

c# update clock display once per second

For my heating controls project I display a clock on my main screen. It is just a label which I set the time in. Previously I had used a System.Threading.Timer with an interval of 50ms to do the updates, and this works fine, but on mono on the Raspberry Pi it takes up too much CPU. Top was showing that the process was taking 8-10% of the CPU when at idle.

The problem is that if you set a timer to run every 1000ms, sometimes expirations will be slightly longer, meaning that if you watch the clock every now and then it will skip displaying a second. If you go for something like 900ms update, that doesn't work either, you get 9 short seconds and one long one.

Googling turned up this stackoverflow article, and it contains the answer that got me started, but I wanted it to be a little more versatile. I wanted a timer that I could configure to fire every 'x' ms, and I could either set it to 1000, for each second, or if that was still too CPU hungry I could set it to 60000, which would only update the clock once per minute.

In my form_load I initialise the timer.

            clockUpdateTimer = new System.Timers.Timer();
            clockUpdateTimer.Elapsed += clockTimer_Elapsed;
            clockUpdateTimer.Interval = 100;

I set the initial interval to 100ms which means my clockTimer_Elapsed() method gets its first call pretty much straight away.

        private long updateMainScreenIntervalMs = 1000;

        private void clockTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
            //start the timer to run until 200ms after the next clock second boundary
            long timeInMs = System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
            long wait = (updateMainScreenIntervalMs * (timeInMs / updateMainScreenIntervalMs + 1)) - timeInMs;
            //Debug.WriteLine("wait ms: " + wait);
            clockUpdateTimer.Interval = (int)wait + 200;

This method firstly stops the existing timer, and then calls my callback method to update the label (TimerCallback()). Next I get timeInMs which is the current system time in milliseconds. Then I do some maths which essentially finds the nearest full cycle of time, and subtracting the current time from this value figures out how long we have to wait until this time. 
Then I start the timer with that time. For a 1 second interval it typically waits about 750ms. 

Any improvements welcome, if it helps you please post a comment!

No comments: