Do You Know Your Message Queue from Your Windows Procedure?

If you’re a Windows developer and don’t know this then you need to learn it, now. Here’s why:

I’ve been examining some legacy code that has a Timer event with some lengthy code that contains DoEvents calls. A simplified version looks something like this:


Private Sub tmrProcess_Timer()
'Run some slow processing code here
DoEvents
'More slow code here
DoEvents
'Lots more slow code and the occasional DoEvents here
If booComplete Then
tmrProcess.Enabled = False
End If
End Sub

The timer has it’s Interval set to 250 and the slow code could take up to thirty or so seconds to complete.

Given that VB6 is single threaded and that timer messages are low priority the question is if it is at all possible for the Timer event to be re-entered during a DoEvents call or will the VB6 runtime block execution of a Timer event if the Timer event is currently executing?

The reference above contains some interesting clues as to the answer. In particular it states that WM_PAINT messages are combined into a single message but there is no mention of whether or not WM_TIMER messages are combined.

So the best way to find out is to write a test program:


Option Explicit

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Private Counter As Integer
Private StopTimer As Boolean

Private Sub cmdStart_Click()
txtResults.Text = "Time: " & Format(Now, "HH:mm:ss") & " Start"
Counter = 0
StopTimer = False
tmrTest.Interval = 500
tmrTest.Enabled = True
End Sub

Private Sub cmdStop_Click()
txtResults.Text = txtResults.Text & vbCrLf & "Time: " & Format(Now, "HH:mm:ss") & " Counter: " & Counter & " Stop"
StopTimer = True
End Sub

Private Sub tmrTest_Timer()
Static Index As Integer
Index = Index + 1
Counter = Counter + 1
txtResults.Text = txtResults.Text & vbCrLf & "Time: " & Format(Now, "HH:mm:ss") & " Index: " & Index & " Counter: " & Counter
DoEvents
Sleep 1000
DoEvents
Sleep 1000
DoEvents
txtResults.Text = txtResults.Text & vbCrLf & "Time: " & Format(Now, "HH:mm:ss") & " Index: " & Index & " Counter: " & Counter
If StopTimer = True Then
tmrTest.Enabled = False
txtResults.Text = txtResults.Text & vbCrLf & "Time: " & Format(Now, "HH:mm:ss") & " Stopped"
End If
End Sub

Here’s my test results:

Time: 15:54:38 Start
Time: 15:54:38 Index: 1 Counter: 1
Time: 15:54:40 Index: 1 Counter: 1
Time: 15:54:41 Index: 2 Counter: 2
Time: 15:54:43 Index: 2 Counter: 2
Time: 15:54:43 Index: 3 Counter: 3
Time: 15:54:44 Counter: 3 Stop
Time: 15:54:45 Index: 3 Counter: 3
Time: 15:54:45 Stopped

What I think is happening is that additional calls to the Timer event are being cancelled by the VB6 runtime Windows Procedure so that only one Timer event can run at once. Then when the Timer is cancelled any remaining WM_TIMER messages are either removed from the queue, ignored, or fail silently because the function they are calling is no longer valid.

Because the Windows Procedure is the core of a client program and are not required to be standardized it would be very interesting to compare how different programs, and especially different programming language runtimes, handle the same situation.