Johnno Nolan
Johnno Nolan

Reputation: 29659

Is there an equivalent to Thread.Sleep() in VBA

Is there an equivalent to Thread.Sleep() in Access VBA?

Upvotes: 48

Views: 104046

Answers (10)

GWD
GWD

Reputation: 3998

Cross-Platform Solution

The accepted answer will not work in 64-bit VBA. Also, it won't work on Mac.

Thanks @Cristian Buse for pointing out Mac compatibility in your comment!

Achieving full Mac compatibility requires importing the platform-dependent library function for suspending thread execution, that is usleep on Mac and Sleep on Windows. Because usleep takes it's argument in microseconds and Sleep uses milliseconds, it is necessary to declare a custom Sub that deals with the conversion.

Importing the library functions and declaring the Sub can be done as presented in the following.

This solution emerged from a collaboration of myself and Cristian Buse on an adaption of his original solution, to avoid integer overflow and to allow Sleep times of more than &HFFFFFFFF microseconds (~71 minutes) on Mac:

#If Mac Then
    #If VBA7 Then
        Private Declare PtrSafe Sub USleep Lib "/usr/lib/libc.dylib" Alias "usleep" (ByVal dwMicroseconds As Long)
    #Else
        Private Declare Sub USleep Lib "/usr/lib/libc.dylib" Alias "usleep" (ByVal dwMicroseconds As Long)
    #End If
#Else
    #If VBA7 Then
        Private Declare PtrSafe Sub MSleep Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
    #Else
        Private Declare Sub MSleep Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
    #End If
#End If
'Sub providing a Sleep API consistent with Windows on Mac (argument in ms)
'Authors: Guido Witt-Dörring, https://stackoverflow.com/a/74262120/12287457
'         Cristian Buse,      https://stackoverflow.com/a/71176040/12287457
Public Sub Sleep(ByVal dwMilliseconds As Long)
    #If Mac Then 'To avoid overflow issues for inputs > &HFFFFFFFF / 1000:
        Do While dwMilliseconds And &H80000000
            USleep &HFFFFFED8
            If dwMilliseconds < (&H418937 Or &H80000000) Then
                dwMilliseconds = &H7FBE76C9 + (dwMilliseconds - &H80000000)
            Else: dwMilliseconds = dwMilliseconds - &H418937: End If
        Loop
        Do While dwMilliseconds > &H418937
            USleep &HFFFFFED8: dwMilliseconds = dwMilliseconds - &H418937
        Loop
        If dwMilliseconds > &H20C49B Then
            USleep (dwMilliseconds * 500& Or &H80000000) * 2&
        Else: USleep dwMilliseconds * 1000&: End If
    #Else 'Windows
        MSleep dwMilliseconds
    #End If
End Sub

Now Sleep will be available on both, Windows and Mac and in 32- as well as 64-bit environments.

It can be called like this:

Sub ExampleSleepCall()
    Sleep 1000 'Suspends thread execution for 1 second
    'Calling application will freeze completely for that amount of time!
    'If this is undesired, look here: https://stackoverflow.com/a/74387976/12287457
End Sub

Note that:

  1. While Sleep does take its argument in milliseconds, its resolution is not actually 1 millisecond.
  2. On Mac, the maximum usleep duration is limited by MAX_UINT = &HFFFFFFFF microseconds or about 4294.97 seconds (~71 minutes). This is why the custom Sleep sub for Mac will call usleep multiple times for input values dwMilliseconds > &H418937, to avoid integer overflow issues.
  3. On Windows (and on Mac with the custom Sleep sub), the maximum Sleep duration is limited by MAX_UINT milliseconds, about 4294967.3 seconds. (~49.71 days)
  4. On both Windows and Mac, calling Sleep with millisecond values greater than MAX_LONG = &H7FFFFFFF (= 2147483647) requires passing it negative VBA Long values, with the maximum Sleep duration achieved by calling it like this: Sleep -1 (=Sleep &HFFFFFFFF)

Note that this way of pausing the execution has severe drawbacks in VBA!

Most importantly, all Microsoft Office applications run VBA code in the same thread as the main user interface. This means, calling Sleep essentially freezes the entire application (It will show as "not responding" in Task-Manager!). There is no way of recovering from this state without waiting for the time to pass or force quitting the Application and restarting it. Excels Application.Wait suffers from the same issue. While the app will not show as not responding in Task Manager in this case, it will be just as unresponsive to the user.

A way to circumvent this problem is calling DoEvents in a loop, as other people have already pointed out. However, this comes with another issue. Because the application will try to execute VBA code as fast as possible, DoEvents is called at the maximum achievable rate essentially saturating the CPU completely on that single thread, leading to high, unnecessary CPU and power usage and potentially slowing down other more important tasks in the UI.

To learn about the best way to pause VBA execution, look at this answer.

Upvotes: 2

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112334

If the code is executing in a Form, you can use the built-in Timer of the Form.

Start the timer of the Form with

TimerInterval = 1000 ' Delay in ms

Declare an event handler:

Private Sub Form_Timer()
    TimerInterval = 0 ' Stop the timer if don't want to repeat the event each second

    ' Execute your code here
End Sub

Upvotes: 0

Tony L.
Tony L.

Reputation: 19396

If you use Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long), you may get this error in an object module.

enter image description here

If so, you can declare it as private:

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

Upvotes: 3

user3298002
user3298002

Reputation: 87

It is possible to use the Excel Wait() procedure from Access VBA.

The first step is to ensure that the Excel library is referenced from your project.

When that's done the following code will work to wait for ten seconds :

Call Excel.Application.Wait(Time:=DateAdd("s",10,Now()))

Upvotes: 2

The Data Brewer
The Data Brewer

Reputation: 106

All of the rest of the methods to make Excel wait result in Excel becoming completely unresponsive. The solution to make Excel wait while ensuring a responsive UI is to call this wait Sub with the number of seconds to wait.

    Sub Wait(seconds As Integer)
      Dim now As Long
      now = Timer()
      Do
          DoEvents
      Loop While (Timer < now + seconds)
    End Sub

Upvotes: 8

Gaffi
Gaffi

Reputation: 4367

I use this in Excel and it works great:

Application.Wait DateAdd("s", 1, Now())

DateAdd() is a function that set a time, relative to Now() (in this case - you can use other values as your argument), "s" is the time measure (seconds in this case), and the increment is 1. So here, the function call is telling the application to wait 1 second.

See also for more detail about the use of the DateAdd function.

Upvotes: 4

Zorba Eisenhower
Zorba Eisenhower

Reputation: 69

A couple of amendments are required to get the code to work. The code below is the corrected version.

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

Sub SleepVBA() 
Sleep 1000 'Implements a 1 second delay 
End Sub 

Upvotes: 6

sebastien leblanc
sebastien leblanc

Reputation: 675

Adding

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

somehow created additional problems somewhere else in my code. I ended up using this function that I found on an other forum and tweeked a bit:

Function WaitTime(n As Double)
'Function that wait an amount of time n in seconds
TWait = Time
TWait = DateAdd("s", n, TWait)
Do Until TNow >= TWait
     TNow = Time
Loop
End Function

hope this helps :)

Upvotes: 1

Ot&#225;vio D&#233;cio
Ot&#225;vio D&#233;cio

Reputation: 74250

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

Use the following syntax to call the Sleep function:

Sub Sleep()
Sleep 1000 'Implements a 1 second delay
End Sub 

Upvotes: 65

DontFretBrett
DontFretBrett

Reputation: 1165

Another way without using kernel32:

Dim started As Single: started = Timer

Do: DoEvents: Loop Until Timer - started >= 1

Upvotes: 9

Related Questions