Search code examples
c#servicetimerdst

Timer callback raised every 24 hours - is DST handled correctly?


I just thought about the way I solved a service which runs a task every 24 hours and how DST could possibly hurt it.

To run the task daily, I used a System.Threading.Timer with a period of 24 hours, like this:

_timer = new System.Threading.Timer(TimerCallback, null,
    requiredTime - DateTime.Now, new TimeSpan(24, 0, 0));

Suddenly thinking about daylight saving time correction I had three thoughts:

  • DST is useless and we should get rid of it.
  • Does the Timer handle this correctly? I think not - it simply waits 24 hours - no matter if the clock has changed. It just waits it's specified period and then calls the TimerCallback again.
  • DST is useless and we should really get rid of it.

Is my second thought correct? If yes, what can I do to avoid this problem? The task must not be run one hour later or earlier if a DST correction has happened.


Solution

  • If anyone got an answer without the need for such a message loop

    The SystemEvents class already provides its own hidden window and dispatcher loop if you don't provide one yourself. It may seem magical that it knows that you have one, but it goes by a well established contract in Windows programming. It uses Thread.GetApartmentState() on the thread you use to add the event handler. It that returns STA, like it does in any GUI app, then it trusts that your program implements the STA contract and pumps a message loop and SystemEvents doesn't do anything special.

    If it returns MTA, like it does in console mode app or service then it assumes that your program doesn't have a dispatcher loop and supports threading like MTA promises and starts up a new thread. You can see that thread back in the debugger's Debug + Windows + Threads window, the thread's name is ".NET SystemEvents". That thread creates a hidden window and pumps a loop, equivalent to Application.Run(). You can see that window back as well with Spy++, its name is ".NET-BroadcastEventWindow.xxxx".

    Notable is that this behavior is responsible for a lot of GUI programs failing badly, typically on the UserPreferenceChanged event in a GUI app and typically when the user unlocks the workstation. This happens when the program creates a splash screen on a worker thread that isn't STA. SystemEvents assumes that the program needs help and creates that helper thread. Which now fires the events on the wrong thread, one that the program uses to update its UI. It also goes wrong if it an STA thread but that thread is allowed to exit, SystemEvents tries to fire the event on a thread that isn't there anymore and falls back to a TP thread if that failed. That's all very, very bad in a GUI app, deadlock is a common outcome.

    But of course in your case you very much like the way SystemEvents class works, you really want that helper thread so you don't have to write it yourself. Just keep in mind that the TimeChanged event fires on a completely arbitrary thread that is unrelated to any threads that your service started, so properly interlocking is certainly required. Same problem you've got with your own btw.

    Do consider the simple solution. You just need to calculate the timer interval from the difference between tomorrow's wall clock time and today's time. In other words, absolute time, not incremental. Which will produce 23 or 25 hours if it spans a DST change.