29 May 2023

Sleep doesn't wait?

Once in a while you might have created a quick and dirty program that repeatedly prints some value from inside the Sleep message loop. However, there is something funny about that. Shouldn't Sleep wait until there is a message in the queue? So, how can you print something repeatedly? How can Sleep return from its wait state?

The issue
The next small program shows the issue. It opens a window and enters the message loop while printing a dot each time. (I wanted to print the number of the message (_Mess) that is retrieved from the queue, but that isn't possible. The _Mess variable is set in the window procedure and is not part of the Sleep (GetEvent or PeekEvent) command. In addition, GB32 filters the message and not all message numbers are stored in _Mess. So, instead, we print a dot each time the loop is executed.)

OpenW 1
PrintScroll = True
PrintWrap = True
Do
  Print ".";
  Sleep
Until Me Is Nothing

According to the documentation Sleep should 'sleep' until there is a message available. However, the Print command is executed frequently at a pretty high speed printing dots in the window. This shouldn't happen, should it?

Now, lets copy the code above into the IDE, save it and execute the code as a stand-alone EXE. For this choose the 'Launch EXE' button on the toolbar:

SleepNoWait

Now we see something completely different: in the stand-alone EXE the Print command is not executed repeatedly; now Sleep does wait.

Difference between IDE and standalone EXE
When a program is run from inside the IDE the Sleep command doesn't seem to wait, because it returns repeatedly. However, the clue here is the word 'seem', because Sleep does actually wait (as does GetEvent), but it receives WM_TIMER messages at a pretty high speed. These WM_TIMER messages are missing in the stand-alone EXE. So, obviously, the IDE must be responsible for the WM_TIMER messages and indeed it is.

The IDE is to blame
The IDE uses several timers for different purposes. Well known timers are Gfa_Minute and Gfa_Second, that fire every minute or second resp. These two timers are disabled before a program is run (F5), so these are not the cause of the issue. However, there are two more IDE timers that are used to update UI elements like the toolbar buttons, for instance. Every 200ms the clipboard is checked to see if there is text available. If so, the toolbar's Paste button is enabled. Another timer is used to update the sidebar, this one fires every 100ms.

As it happens, these timers are not disabled before a program is run from within the IDE.These timers keep firing their WM_TIMER messages to the thread's queue, which are read by our Sleep in the program's message loop. Our program is executed in the same thread as the IDE and thus takes over the retrieving and dispatching of messages. While running a program, the IDE's message loop isn't executed any longer and all the messages for the IDE are obtained by the program's message loop. After quitting the program the IDE returns to its main message loop and takes over again.

Conclusion
Be aware that the number of times Sleep and GetEvent return from their wait state depends on the timer resolution of the IDE's timers that are still active while running the program.