4

I am currently working on a time sensitive program, where is is important that i do a reading from a device every second. I am currently using a Timer for this, i have however found that the time steady increases the interval with 1 ms pr tick. This can cause a problem as the program will run for a long time.

Read Exe Time is how long the code took to execute.

Performing reading is at start each elapsed event.

How can i ensure that the clock will not increase, i can understand that the tick might not be on the exact millisecond, but that i keep climbing seems weird. and my code do not take the full 1000 ms to execute, so that is not it. After 16.6 minutes will the readings have been pushed a whole second.

Read Exe Time: 574.0637 ms
Performing reading: 22:58:39.696
Read Exe Time: 571.9422 ms
Performing reading: 22:58:40.697
Read Exe Time: 595.5333 ms
Performing reading: 22:58:41.697
Read Exe Time: 566.2602 ms
Performing reading: 22:58:42.698
Read Exe Time: 568.2275 ms
Performing reading: 22:58:43.698
Read Exe Time: 569.7573 ms
Performing reading: 22:58:44.700
Read Exe Time: 561.655 ms
Performing reading: 22:58:45.701
Read Exe Time: 567.8385 ms
Performing reading: 22:58:46.702
Read Exe Time: 584.8305 ms
Performing reading: 22:58:47.703
Read Exe Time: 588.754 ms
Performing reading: 22:58:48.703
Read Exe Time: 560.8154 ms
Performing reading: 22:58:49.704
Read Exe Time: 567.9324 ms
Performing reading: 22:58:50.705
Read Exe Time: 579.1354 ms
Performing reading: 22:58:51.706
Read Exe Time: 563.0227 ms
Performing reading: 22:58:52.707
Read Exe Time: 569.557 ms
Performing reading: 22:58:53.708
Read Exe Time: 560.707 ms
Performing reading: 22:58:54.708
Read Exe Time: 574.6268 ms
Performing reading: 22:58:55.709
Read Exe Time: 570.4872 ms
Performing reading: 22:58:56.710
Read Exe Time: 574.8388 ms
Performing reading: 22:58:57.710
Read Exe Time: 573.2054 ms
Performing reading: 22:58:58.710
Read Exe Time: 578.6189 ms
Performing reading: 22:58:59.711
Read Exe Time: 565.7442 ms
Performing reading: 22:59:0.711
Read Exe Time: 564.7523 ms
Performing reading: 22:59:1.712
Read Exe Time: 575.8134 ms
Performing reading: 22:59:2.713
Read Exe Time: 570.9416 ms
Performing reading: 22:59:3.713
Read Exe Time: 573.1493 ms
Performing reading: 22:59:4.714
Read Exe Time: 570.2831 ms
Performing reading: 22:59:5.714
Read Exe Time: 568.2672 ms
Performing reading: 22:59:6.715
Read Exe Time: 586.5607 ms
Performing reading: 22:59:7.715
Read Exe Time: 588.0465 ms
Performing reading: 22:59:8.715
Read Exe Time: 574.1118 ms
Performing reading: 22:59:9.716

The code

private void StartMeter()
{
    this.Meter.Start();

    this.ReadingTime = new Timer(1000);
    this.ReadingTime.Elapsed += new ElapsedEventHandler(PerformReading);
    this.ReadingTime.Start();
}

    private void PerformReading(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("Performing reading: " + DateTime.Now.Hour + ":" +  DateTime.Now.Minute + ":" +  DateTime.Now.Second + "." +  DateTime.Now.Millisecond);

        // Code here
    }

Update: If i just set the time to 999 ms will it steadily decrease instead

Performing reading: 23:4:50.527
Read Exe Time: 562.7729 ms
Performing reading: 23:4:51.527
Read Exe Time: 566.8178 ms
Performing reading: 23:4:52.527
Read Exe Time: 562.3829 ms
Performing reading: 23:4:53.526
Read Exe Time: 567.9165 ms
Performing reading: 23:4:54.526
Read Exe Time: 561.1329 ms
Performing reading: 23:4:55.525
Read Exe Time: 562.9359 ms
Performing reading: 23:4:56.525
Read Exe Time: 560.4151 ms
Performing reading: 23:4:57.524
Read Exe Time: 561.0302 ms
Performing reading: 23:4:58.524
Read Exe Time: 561.5756 ms
Performing reading: 23:4:59.524
Read Exe Time: 565.2936 ms
Performing reading: 23:5:0.523
Read Exe Time: 561.8903 ms
Performing reading: 23:5:1.523
Read Exe Time: 561.8768 ms
Performing reading: 23:5:2.523
Read Exe Time: 562.3904 ms
Performing reading: 23:5:3.523
Read Exe Time: 562.3363 ms
Performing reading: 23:5:4.523
Read Exe Time: 561.6288 ms
Performing reading: 23:5:5.523
Read Exe Time: 560.4596 ms
Performing reading: 23:5:6.522
Read Exe Time: 562.34 ms
Performing reading: 23:5:7.522
Read Exe Time: 561.5994 ms
Performing reading: 23:5:8.522
Read Exe Time: 561.6811 ms
Performing reading: 23:5:9.521
Read Exe Time: 561.7427 ms
Performing reading: 23:5:10.521
Read Exe Time: 561.8044 ms
Performing reading: 23:5:11.520
Read Exe Time: 561.6246 ms
Performing reading: 23:5:12.520
Read Exe Time: 560.5753 ms
Performing reading: 23:5:13.520
Read Exe Time: 563.8604 ms
Performing reading: 23:5:14.520
Read Exe Time: 562.2606 ms
Performing reading: 23:5:15.534
Read Exe Time: 560.8377 ms
Performing reading: 23:5:16.534
Read Exe Time: 560.4553 ms
Performing reading: 23:5:17.534
Read Exe Time: 562.5534 ms
Performing reading: 23:5:18.533
Read Exe Time: 563.0269 ms
Performing reading: 23:5:19.532
Read Exe Time: 561.2851 ms
Performing reading: 23:5:20.532
Read Exe Time: 560.3442 ms
Performing reading: 23:5:21.531
Read Exe Time: 561.5201 ms
Performing reading: 23:5:22.530
Read Exe Time: 560.609 ms
Performing reading: 23:5:23.530

Edit: For @PeterLuu

Performing reading: 22:44:45.13
Performing reading: 22:44:45.449
Performing reading: 22:44:45.879
Performing reading: 22:44:46.320
Performing reading: 22:44:46.761
Performing reading: 22:44:47.192
Performing reading: 22:44:47.631
Performing reading: 22:44:48.74
Performing reading: 22:44:48.577
Performing reading: 22:44:49.49
Performing reading: 22:44:49.637

And the code

private void StartMeter()
{
    DateTime now = DateTime.UtcNow;
    NextTickTimeWholeSeconds = new DateTime(now.Ticks - (now.Ticks % TimeSpan.TicksPerSecond), now.Kind);

    this.Meter.Start();

    this.ReadingTime = new Timer(1000);
    this.ReadingTime.Elapsed += new ElapsedEventHandler(PerformReading);
    this.ReadingTime.Start();
    ReadingTime.Interval = GetTimeToNextSecond();
}

private double GetTimeToNextSecond()
{
    NextTickTimeWholeSeconds = NextTickTimeWholeSeconds.AddSeconds(1);
    var interval = NextTickTimeWholeSeconds - DateTime.UtcNow;
    return interval.Milliseconds < 1 ? GetTimeToNextSecond() : interval.Milliseconds;
}

private void PerformReading(object sender, ElapsedEventArgs e)
{
    Console.WriteLine("Performing reading: " + DateTime.UtcNow.Hour + ":" + DateTime.UtcNow.Minute + ":" + DateTime.UtcNow.Second + "." + DateTime.UtcNow.Millisecond);

    // My code takes about 500-600 ms

    ReadingTime.Interval = GetTimeToNextSecond();
}
Androme
  • 2,399
  • 4
  • 43
  • 82
  • "_the time steady increases the interval with 1 ms pr tick_" Maybe I misunderstood, but it sounds that the interval does not increase or decrease, it is just about `1.001` or `0.999` s. – AlexD Apr 18 '15 at 21:09
  • Yes that is correct, or 1.0000-1.0001 might be more correct – Androme Apr 18 '15 at 21:12
  • You probably want [a higher resolution timer](http://stackoverflow.com/questions/7137121/c-sharp-high-resolution-timer). And may possibly have to resort to using com calls. – Francisco Aguilera Apr 18 '15 at 21:12
  • 2
    A normal Timer won't give you this kind of accuracy anyway. Recalibrate it each tick from DateTime.Now. But when other processes get busy you can still have overruns of several 100 ms. – H H Apr 18 '15 at 21:12
  • The problem is not as much that is a few millisecond off, the problem is that bases the new tick on it, so the distance from the original millisecond keeps increasing. – Androme Apr 18 '15 at 21:24
  • That's why you need `DateTime.Now` and a `DateTime basePoint;` – H H Apr 18 '15 at 21:42
  • Switching to `DateTime.UtcNow` instead of `DateTime.Now` will help things be more accurate, `UtcNow` takes less time to return because it does not need to calculate timezones. – Scott Chamberlain Apr 18 '15 at 21:47
  • 1
    You have to keep in mind that your machine is not acting normally. The timer precision is far to good, you won't reproduce these results on a normal machine. Another program on your machine has reprogrammed the timer interrupt interval. Chrome does this for example. Best to focus on that sudden jump from 23:5:14.520 to 23:5:15.534, that's normal and what you can expect. – Hans Passant Apr 18 '15 at 21:53
  • Possible your device have another mode, in wich it push data to client with its internal time interval? – gabba Apr 18 '15 at 22:02

2 Answers2

3

You can base it off of the time until the next second (e.g. if there is only 890 milliseconds until the next second on the dot) and restart the timer with that interval every iteration to prevent drifting. Adapted from a previous answer by Jared here. This does not offer precision to the exact millisecond, but it will prevent your times from drifting and the event will always fire "on the dot".

Edit: Removed an unnecessary line - You actually don't need to call Start() twice, just changing the interval is sufficient since changing the interval restarts the timer.

Edit 2: Made some changes to make it more accurate in edge cases (e.g. prevent firing multiple times in a row).

public class Meter
{
    private Timer ReadingTime;
    private DateTime NextTickTimeWholeSeconds;

    public Meter() {
        DateTime now = DateTime.UtcNow;
        NextTickTimeWholeSeconds = new DateTime(now.Ticks - (now.Ticks % TimeSpan.TicksPerSecond), now.Kind);

        ReadingTime = new Timer();
        ReadingTime.AutoReset = false;
        ReadingTime.Elapsed += new ElapsedEventHandler(PerformReading);
        ReadingTime.Interval = GetTimeToNextSecond();
    }

    public void StartMeter()
    {
        ReadingTime.Start();
    }

    private double GetTimeToNextSecond()
    {
        NextTickTimeWholeSeconds = NextTickTimeWholeSeconds.AddSeconds(1);
        var interval = NextTickTimeWholeSeconds - DateTime.UtcNow;
        return interval.Milliseconds < 1 ? GetTimeToNextSecond() : interval.Milliseconds;
    }

    private void PerformReading(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("Performing reading: " + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second + "." + DateTime.Now.Millisecond);
        ReadingTime.Interval = GetTimeToNextSecond();
    }
}

Example Output:

Performing reading: 18:11:47.10
Performing reading: 18:11:48.4
Performing reading: 18:11:49.10
Performing reading: 18:11:50.6
Performing reading: 18:11:51.9
Performing reading: 18:11:52.5
Performing reading: 18:11:53.10
Performing reading: 18:11:54.5
Performing reading: 18:11:55.9
Performing reading: 18:11:56.7
Performing reading: 18:11:57.7
Performing reading: 18:11:58.7
Performing reading: 18:11:59.7
Performing reading: 18:12:0.8
Performing reading: 18:12:1.7
Performing reading: 18:12:2.6

... about 50 seconds later ...

Performing reading: 18:12:50.1
Performing reading: 18:12:51.0
Performing reading: 18:12:52.0
Performing reading: 18:12:53.0
Performing reading: 18:12:53.999
Performing reading: 18:12:55.0
Performing reading: 18:12:56.0
Performing reading: 18:12:56.999
Performing reading: 18:12:58.0
Performing reading: 18:12:59.0
Performing reading: 18:13:0.0
Performing reading: 18:13:0.999
Performing reading: 18:13:2.0
Performing reading: 18:13:3.1
Performing reading: 18:13:4.0
Performing reading: 18:13:5.0
Performing reading: 18:13:6.0
Performing reading: 18:13:6.999
Performing reading: 18:13:8.0
Performing reading: 18:13:9.0
Performing reading: 18:13:10.0
Performing reading: 18:13:11.0
Performing reading: 18:13:12.1
Performing reading: 18:13:13.0
Performing reading: 18:13:13.999
Performing reading: 18:13:15.0
Performing reading: 18:13:16.0
Performing reading: 18:13:16.999
Performing reading: 18:13:18.0
Performing reading: 18:13:19.1
Performing reading: 18:13:20.0
Performing reading: 18:13:21.0
Performing reading: 18:13:21.999
Performing reading: 18:13:23.0
Performing reading: 18:13:24.0
Performing reading: 18:13:25.0
Performing reading: 18:13:26.0
Performing reading: 18:13:27.0
Performing reading: 18:13:28.0
Performing reading: 18:13:29.0
Community
  • 1
  • 1
Peter Luu
  • 446
  • 2
  • 10
  • With this edit i get the following: Performing reading: 0:17:21.3 Performing reading: 0:17:21.183 Performing reading: 0:17:21.370 Performing reading: 0:17:21.560 Performing reading: 0:17:21.746 Performing reading: 0:17:22.187 Performing reading: 0:17:22.611 Performing reading: 0:17:23.267 Performing reading: 0:17:23.57 Performing reading: 0:17:23.314 Performing reading: 0:17:23.314 – Androme Apr 18 '15 at 22:18
  • Sorry, I changed it a couple of times - are you using the code in the most recent version I put up there (where the function is called GetTimeToNextSecond)? – Peter Luu Apr 18 '15 at 22:21
  • Yes, i used the current version – Androme Apr 18 '15 at 22:25
  • I can't imagine why it would execute more than once per second - does that happen even if you just initialize the code with something like static void Main(string[] args) { var meter = new Meter(); meter.StartMeter(); System.Threading.Thread.Sleep(10000000); } ? – Peter Luu Apr 18 '15 at 22:32
  • @DoomStone can you put in the `ReadingTime.AutoReset = false` line into your code and see if it works for you then? – Peter Luu Apr 18 '15 at 22:56
1

Using CodingBarfield's response, worked very well for me some time ago.

Raise event in high resolution interval/timer

Sorry for not giving more details, is nicely explained there.

Community
  • 1
  • 1
Olaru Mircea
  • 2,570
  • 26
  • 49
  • Might want to check System.Threading.Timer as I think that is a wrapped around the pinvoke your referenced answer uses. – Andy Apr 18 '15 at 23:05