To Main Page

View in Russian

Using of wait functions in Visual Basic

Rather often Visual Basic programmers need to use Windows API functions which delay program execution until the occurence of certain event. Let's not discuss here the question, when and how this need appears - this is the theme for a separate article. Also, let's not stop on description of parameters and return values of discussed functions: the interested always can obtain this information from MSDN.

Here is the list of these functions:

Sleep SleepEx
WaitForSingleObject WaitForSingleObjectEx
WaitForMultipleObjects WaitForMultipleObjectsEx
MsgWaitForMultipleObjects MsgWaitForMultipleObjectsEx

We shall only look throw the functions from the first column of the table. Other functions are applied rather seldom, and in the final analysis the problem of using of these functions in Visual Basic can be solved in a similar way to stated below.

So, what problems may encounter the programmer using above API functions with?

Fortunately, there is only one problem, but it is rather serious. The thing is that the programs written on Visual Basic with a small exception are executing in one OS thread. It means that when one of wait functions starts, the "life" of the program completely stops: the visual interface freezes, the buttons became unclickable, and TaskManager reports "Not Responding". May happen worse: some OS components interact with user applications in synchronous mode, and sometimes it results in OS Shell freezing during significant time.

Each of the listed above wait functions has the parameter, which specifies the amount of wait time, after expiration of which the program execution continues. If this interval doesn't exceed 1 - 2 seconds, the wait functions can be used without problems, but if wait period is larger or infinite, the another approach must be applied.

Fortunately, there is MsgWaitForMultipleObjects function, which is capable to "wake" when new message appears in thread messages queue. And it is what we need! Let's remember that the system is called Windows, so it consists of windows, and the windows interact each other by sending and receiving messages. Missing out the details, let's note that any event which the window representing the program must respond on (button click, moving , resizing, scrolling and so on) results in appearance of new message in the thread queue. The Visual Basic program will process this message only when realizes one of these conditions:

  • The program does not execute any instruction (that is the execution of code in all procedures, functions, methods, properties is completed)
  • The program have called DoEvents function.

Generalizing told above, it is possible to formulate the rules of using wait functions in Visual Basic programs:

  • Wait functions can be used without restrictions when wait period is small
  • If wait interval is significant or unknown, only MsgWaitForMultipleObjects function may be used
  • DoEvents function must be called at every new message appearance in thread queue.

Now is the time to illustrate told with an example. The offered MsgWaitObj function may be used as a non-blocking equivalent of Sleep, WaitForSingleObject and WaitForMultipleObjects functions.

Option Explicit
'*    (c) 1999-2000 Sergey Merzlikin        *

Private Const STATUS_TIMEOUT = &H102&
Private Const INFINITE = -1& ' Infinite interval
Private Const QS_KEY = &H1&
Private Const QS_MOUSEMOVE = &H2&
Private Const QS_MOUSEBUTTON = &H4&
Private Const QS_POSTMESSAGE = &H8&
Private Const QS_TIMER = &H10&
Private Const QS_PAINT = &H20&
Private Const QS_SENDMESSAGE = &H40&
Private Const QS_HOTKEY = &H80&
Private Declare Function MsgWaitForMultipleObjects Lib "user32" _
        (ByVal nCount As Long, pHandles As Long, _
        ByVal fWaitAll As Long, ByVal dwMilliseconds _
        As Long, ByVal dwWakeMask As Long) As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

' The MsgWaitObj function replaces Sleep, 
' WaitForSingleObject, WaitForMultipleObjects functions.
' Unlike these functions, it
' doesn't block thread messages processing.
' Using instead Sleep:
'     MsgWaitObj dwMilliseconds
' Using instead WaitForSingleObject:
'     retval = MsgWaitObj(dwMilliseconds, hObj, 1&)
' Using instead WaitForMultipleObjects:
'     retval = MsgWaitObj(dwMilliseconds, hObj(0&), n),
'     where n - wait objects quantity,
'     hObj() - their handles array.

Public Function MsgWaitObj(Interval As Long, _
            Optional hObj As Long = 0&, _
            Optional nObj As Long = 0&) As Long
Dim T As Long, T1 As Long
If Interval <> INFINITE Then
    T = GetTickCount()
    On Error Resume Next
    T = T + Interval
    ' Overflow prevention
    If Err <> 0& Then
        If T > 0& Then
            T = ((T + &H80000000) _
            + Interval) + &H80000000
            T = ((T - &H80000000) _
            + Interval) - &H80000000
        End If
    End If
    On Error GoTo 0
    ' T contains now absolute time of the end of interval
End If
    If Interval <> INFINITE Then
        T1 = GetTickCount()
        On Error Resume Next
     T1 = T - T1
        ' Overflow prevention
        If Err <> 0& Then
            If T > 0& Then
                T1 = ((T + &H80000000) _
                - (T1 - &H80000000))
                T1 = ((T - &H80000000) _
                - (T1 + &H80000000))
            End If
        End If
        On Error GoTo 0
        ' T1 contains now the remaining interval part
        If IIf((T1 Xor Interval) > 0&, _
            T1 > Interval, T1 < 0&) Then
            ' Interval expired
            ' during DoEvents
            MsgWaitObj = STATUS_TIMEOUT
            Exit Function
        End If
    End If
    ' Wait for event, interval expiration
    ' or message appearance in thread queue
    MsgWaitObj = MsgWaitForMultipleObjects(nObj, _
            hObj, 0&, T1, QS_ALLINPUT)
    ' Let's message be processed
    If MsgWaitObj <> nObj Then Exit Function
    ' It was message - continue to wait
End Function

Some comments to the above-stated code:

  1. Why the overflow prevention is required? The thing is that GetTickCount function returns the amount of milliseconds since the operating system was started in form of unsigned double-word integer value (DWord). The maximal DWord value - &HFFFFFFFF. The nearest equivalent of such type in BASIC is Long, but Long always is signed, and its maximal value for positive numbers - &H7FFFFFFF. If return value of GetTickCount function is close to this boundary, the arithmetic overflow exception can occur in the next line of code.
    You will tell that such never happens, as computers so long don't work without reboot (if &H7FFFFFFF milliseconds convert to a habitual time scale, it would equal to about 25 days)? I disagree with you. The reliable program should take into account such opportunity.
    When the computer works so long, that the amount of milliseconds doesn't fit even in DWord, GetTickCount begins from zero. But from the point of view of Visual Basic arithmetic, any exceptions don't occur: simply for -1 follows 0.

  2. Win32API.txt says:


    Basically it is correct if not to take into account that such definition can confuse even the experienced programmer. When this constant appears as a Long-type parameter of API function, it is possible to think, that the function receives the number 65535, but it isn't correct. When the type of a numeric constant is not declared, it is considered, that it's type is Byte, Integer, Long, Single or Double, if the appropriate number fits in area of allowable values of this type. &HFFFF fits in Integer type. But for Integer &HFFFF = -1, and just this number converted into Long type, is passed to API function. Therefore in order to prevent misunderstanding I advise to write this definition this way:

    Const INFINITE = -1&



See also Microsoft Knowledge Base Q231298.

That's all. The above mentioned code can be copied directly from a browser.

This page has been updated last time on 26-Oct-2000
2000 Sergey Merzlikin
Write me: