We\'re getting following problem while using System.Threading.Timer
(.NET 2.0) from a Windows service.
Timothy's explanation is compelling, but System.Threading.Timer already works this way. At least in the Rotor implementation, it uses the value returned by GetTickCount() to calculate when the next callback is due. It isn't terribly likely that the real CLR does this as well, it is more likely to use CreateTimerQueueTimer(). Nevertheless, this API function is also likely to depend on the internal tick incremented by the clock tick interrupt.
At issue is how well the internal clock tick (as returned by GetTickCount() and Environment.TickCount) stays in sync with the absolute wall time (as returned by DateTime.Now). Unfortunately, that's an implementation detail of the HAL on your machine, the Hardware Abstraction Layer. The machine builder can provide a custom HAL, one that was tweaked to work with the machine's BIOS and chipset.
I'd expect trouble if the machine has two timing sources, a clock chip that was designed to track the wall time and keeps ticking even you unplug the machine power, and another frequency available on the chipset that can be divided to provide the clock interrupt. The original IBM AT PC worked that way. The clock chip can usually be trusted, the clock would be grossly off if it isn't accurate. The chipset cannot, cutting corners on the quality of the oscillators is an easy way to save a penny.
Solve your problem by avoiding long periods on your timer and recalculating the next due-time from DateTime.Now in the callback.