1

For implementation of my Windows service, I need to be able to set a timer for a certain duration of system being in the working state. So I originally came up with the following code.

A) Setting up a waitable timer (error checks are omitted):

HANDLE hWTimer = ::CreateWaitableTimer(NULL, FALSE, NULL);

//As an example, set timer to wait for 40 minutes
int nWaitMins = 40;

LARGE_INTEGER li;
ULONGLONG uiWaitMs = (ULONGLONG)nWaitMins * 60LL * 1000LL;
li.QuadPart = -10000LL * uiWaitMs;  //Convert to 100 nanosecond intervals (must be negative for relative time)
::SetWaitableTimer(hWTimer, &li, 0, NULL, NULL, FALSE);     //Don't wake the system

B) Waiting for it (from a worker thread):

//Wait for timer to fire
::WaitForSingleObject(hWTimer, INFINITE);

This works really well with one caveat (if the system is not put into sleep mode or hibernated.) In that case what happens is best illustrated in this diagram:

enter image description here

when what I need it to do is this:

enter image description here

Is there a timer to do what I want here?

PS. Copies of my service submit reports to our web server from multiple workstations. With my technique above I try to randomize submission times to alleviate server workload. Since all workstations are put into sleep and then woken up roughly at the same time, my "randomization" technique doesn't work when machines wake up from sleep.

PS2. I need to point out that I need this to work under Windows XP.

c00000fd
  • 20,994
  • 29
  • 177
  • 400

3 Answers3

2

You need to be notified when the system is entering sleep mode, start here .

When you receive the WM_POWERBROADCAST with PBT_ABTSUSPEND, stop your timer and keep track of how much time left you have. When the PBT_ABTRESUMESUSPEND event is received, start the timer again with the time left.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • Thanks, except how do you address these: 1) `PBT_ABTSUSPEND` may not be broadcast on Windows XP, and 2) `WaitForSingleObject` will most certainly "fire" or "unlock as being signaled" first before I receive `WM_POWERBROADCAST`? In other words, that creates a "race condition." – c00000fd May 16 '15 at 21:56
  • https://msdn.microsoft.com/en-us/library/windows/desktop/aa372721%28v=vs.85%29.aspx - it says it's supported on XP. If WaitForSingleObject is released before the timer expires, you're in excellent shape - just check if enough time has passed. – zmbq May 16 '15 at 22:02
  • Why do your workstations all go to sleep in the same time? If your workstations wait 40 minutes to make a report, scheduled tasks sound better to me. Just schedule your workstations differently. It will not be effected by sleeping, workstations will be wake up to do the report. You can further randomized the report by let each task wait a random time before executing report. – Tim3880 May 16 '15 at 22:04
  • @zmbq: Read user comments at the bottom of that MSDN page. `WaitForSingleObject` will not be released before timer expires, you meant to say "after", correct? If so, how would I know how long the system was suspended from just my code running after `WaitForSingleObject`? And even if it was suspended at all? – c00000fd May 16 '15 at 22:14
  • @Tim3880: Sorry, I'm not following exactly what you suggested. Can you elaborate in your own answer? – c00000fd May 16 '15 at 22:14
  • Use MsgWaitForMultipleObjects to both pump messages and wait for your timer. – zmbq May 17 '15 at 04:27
  • @zmbq: It would still not remove the "race condition." The only complicated way to do it (as I'm doing it now) is to use two waitable timers. One -- it fires when needed, then immediately thereafter start another timer for something like 5 seconds. In the meantime try to catch `PBT_APMRESUMEAUTOMATIC` (note, **not** `PBT_ABTSUSPEND` as you suggested) and save the fact if you receive it & hope that this message arrives within 5 seconds. After that when the second timer fires, check if `PBT_APMRESUMEAUTOMATIC` was received which will tell you that the system was restored from suspension. – c00000fd May 17 '15 at 23:02
  • My method still doesn't remove a chance of a race condition (for instance when an overloaded system may delay dispatching user messages for longer than 5 seconds.) Thus I was hoping for a more reliable method... – c00000fd May 17 '15 at 23:04
  • I don't follow. Why can't you cancel the timer when the system is going to be suspended (ignore it even if it fires at that time - so no race condition), and then set it again after the system is resumed (again - no race condition because there's no timer to fire)? – zmbq May 18 '15 at 19:58
0

If you just want all your workstations send some report every 40 minutes (i can be wrong here), you don't need a Windows service which is difficult to program and debug. A simple scheduled task will do just fine.

To avoid all workstations sending reports in the same time, you just set a random waiting period in your report program, wait 0-20 minutes randomly before send the report. So even you schedule all jobs on each workstations in the same schedule,the reports will not be sent together.

Scheduled tasks can also wake your workstations to send the reports.

Tim3880
  • 2,563
  • 1
  • 11
  • 14
  • I am not a big fan of trusting Microsoft's service to do something that I can do & control in my own service. But even if so, how does it answer my question? – c00000fd May 17 '15 at 02:05
0

Updated.

If you want to wait based on a wall clock time (actual real time independent of system sleep/hibernate), you want to be using Win32 threadpool functions such as CreateThreadPoolTimer or CreateThreadpoolWait.

If you want to wait based on system time (time the computer was actually awake and not sleeping), then any of the Win32 wait functions will likely suffice. This includes WaitForSingleObject and Sleep.

Also, for what it's worth, the C++ condition_variable in Win32 has a method called wait_until, but it's actually implemented with system time.

Finally, many APIs have the possibility of "spurious wake up" or waking up too early. It's up to your code to decide if the conditions have been met. If you need to be precise, the best thing to do is keep track of time before and after your timer callback completed. Then measure the difference. For system time, use GetTickCount64 or GetTickCount. For wall clock time, use (ironically) the API called GetSystemTime. If you detect that you woke up too early, simply schedule the timer to wait the needed length of time again.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • I'm sorry, but what is the difference between "wall clock" and "system time"? – c00000fd May 17 '15 at 02:06
  • `Thread pool functions` were introduced in Windows Vista, and I need it to work in Windows XP as well. – c00000fd May 17 '15 at 02:07
  • Also what is `"APIs have the possibility of "spurious wake up"`? Can you give any examples? Because, you're just investing stuff... – c00000fd May 17 '15 at 02:09
  • **wall clock time** is actual real time regardless of whether the system was asleep or not. A timer/wait based on wall clock time will signal when the "clock on the wall" has reached the specified time. **system time** is the amount of time the system has been awake. When the system is asleep or hibernating, wall clock time continues on, but the system clock is frozen. – selbie May 17 '15 at 05:02
  • If you need to work on XP, you have several choices. Either reschedule your timers based on power events such as the ones zmbq discusses. Or use a Windows Scheduled Task (as Tim3880 says) to signal your process. Or do a polling mechanism - just wake up periodically (e.g. every 5 minutes), check to see if "it's time yet", and then either execute the operation or go back to sleep. – selbie May 17 '15 at 05:05
  • As for spurious wake up, you don't have to worry about an event handle being signaled prematurely, but you should not rely on the timer callback to be an EXACT indication of time if your code is sensitive to this sort of thing. Query the current wall clock time when a timer fires. – selbie May 17 '15 at 05:17
  • Usually you start by learning terminology so that not to confuse yourself in the future. Please review: [SystemTime](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724390%28v=vs.85%29.aspx) which is the same as UTC or GMT time. There's also [LocalTime](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724338(v=vs.85).aspx) that is system time adjusted for your time zone. But all this is irrelevant to my original question... – c00000fd May 17 '15 at 23:09
  • Don't get confused by "local time". Local time is just a representation of wall clock time. GetSystemTime, which I explained above, is really a wall clock time API returns UNC time. When measuring relative time, using the same API between calls will suffice. – selbie May 18 '15 at 02:29