2

I have a piece of code that records the current time (DateTime.UtcNow) as a "deadline", sets up an System.Threading.Timer, and when the timer elapses checks if the deadline has been reached. It does this check by comparing the current time (DateTime.UtcNow) to the recorded deadline.

Here is a simplified code example that illustrates the principle:

class DeadlineChecker
{
  private DateTime deadline;

  public DeadlineChecker()
  {
    int waitingTimeInMilliseconds = 1200;

    TimeSpan waitingTime = TimeSpan.FromMilliseconds(waitingTimeInMilliseconds);
    this.deadline = DateTime.UtcNow + waitingTime;

    System.Threading.Timer timer = new System.Threading.Timer(TimerElapsed);
    timer.Change(waitingTimeInMilliseconds, Timeout.Infinite);
  }

  private void TimerElapsed(object state)
  {
    if (this.HasDeadlineBeenReached())
    {
      [...]  // Do something
    }

    [...] // Cleanup (dispose timer etc.)
  }

  private bool HasDeadlineBeenReached
  {
    get
    {
      // This is only called from TimerElapsed(), so
      // this should always return true, right?
      return (this.deadline <= DateTime.UtcNow);
    }
  }
}

I have now had a case where HasDeadlineBeenReached unexpectedly returns false. It appears that my assumption that HasDeadlineBeenReached always returns true in the example code above is wrong.

Can someone explain why? Before I refactor I would like to understand the issue.

Even taking the documented fact into account that DateTime.UtcNow has a 15ms resolution, I would not have expected that two snapshots of DateTime.UtcNow taken at times X and Y would report a time span that is smaller than what has actually elapsed (Y - X). Even if both snapshots are 15ms off, the time span between them should still be equal to the time that has elapsed due to the timer?

EDIT: What the real code tries to do is to set up a deadline, check that deadline when certain events occur, and "do something" when the deadline has been reached. The "timer elapsed" event is merely one of the many events that trigger the deadline check and is there to guarantee that "do something" happens in the absence of other events. It doesn't matter whether "do something" happens on time or a few milliseconds or even a second later than the deadline - there just needs to be a guarantee that it happens.

EDIT 2: I made a few minor changes to the code example after accepting the answer - bad style, I know, but maybe this makes the question a bit more intelligible to future generations.

herzbube
  • 13,158
  • 9
  • 45
  • 87
  • 4
    Is the machine this code is running on set up to automatically adjust its clock? `UtcNow` is at the mercy of any changes made to the computer's concept of the current time. – Damien_The_Unbeliever Oct 20 '20 at 08:11
  • `DateTime.UtcNow` or `DateTime.Now` retrieve their values from the OS. They aren't affected by application timers. *Timers* though are no more accurate than an OS-defined interval, which is somewhere around 16.5ms. `DateTime`'s precision is around 14-16ms as well, so what you do isn't very accurate – Panagiotis Kanavos Oct 20 '20 at 08:13
  • Related question [C# DateTime.Now precision](https://stackoverflow.com/questions/2143140/c-sharp-datetime-now-precision) – Panagiotis Kanavos Oct 20 '20 at 08:14
  • 1
    What are you trying to do in the first place? Why check the wall clock when the timer fires? BTW timers need to be disposed, so the current code leaks timers already. – Panagiotis Kanavos Oct 20 '20 at 08:18
  • @Damien_The_Unbeliever That's a good hint, thanks. – herzbube Oct 20 '20 at 08:33
  • @PanagiotisKanavos I'm aware of the need for disposing timers, there's a comment in the example that shows this. What I'm trying to do is to set up a deadline, check that deadline when certain events occur, and do something when the deadline has been reached. In the real application the "timer elapsed" event is merely one of the many events that trigger the deadline check. The code example is simplified and contains only the "timer elapsed" event. – herzbube Oct 20 '20 at 08:38
  • Post your actual question then, not the simplified version. Both timers and `DateTime` are inaccurate and trying to check the deadline won't help - you already know it's the target time +/- the timer's *and* clock's accuracy. The question was oversimplified – Panagiotis Kanavos Oct 20 '20 at 08:41
  • Again, what are you trying to do, and why would a 30ms skew after 1200 ms matter? Depending on the problem, the answer may be as simple as removing the check, increasing the timer's frequency - or have to use a completely different API. You can't use normal timers for multimedia payloads (sound, video, graphics, animations) – Panagiotis Kanavos Oct 20 '20 at 08:45
  • Have you debugged what the deadline and now values are when it returns false? – GazTheDestroyer Oct 20 '20 at 08:46
  • @GazTheDestroyer No debugging, no. This is not something that is easily reproducible. The conclusion that the comparison must return false sometimes is the result of log file analysis. – herzbube Oct 20 '20 at 09:28

1 Answers1

2

You wrote:

// This should always return true, right?

No, it will only return true when deadline is in the past. I appreciate that you created a deadline that was 1200ms into the future and then told a timer to fire after 1200ms and are expecting the deadline to be in the past the instant the timer fires, but in reality the timing of timers and the accuracy of the clock is such that you might well reasonably observe your code not firing at the exact milliseconds you expect, and the clock not returning the exact milliseconds you expect, and as a result your deadline is still in the future when the code runs

I suggest you adjust your timing strategy; set your timer for a half the interval you want to be "accurate" to interval, check your deadline to be in the past using the same logic, and tolerate the imprecision (don't promise the user that they can schedule the notification of their meeting with 1 millisecond precision)

Caius Jard
  • 72,509
  • 5
  • 49
  • 80