When (or Should I Say, How Often) to Call DoEvents

This is part 3 of a multi-part series on the DoEvents function.

A brief recap of DoEvents

Calling DoEvents is sometimes a necessary evil.  

Microsoft Access is single-threaded.  The code and the user interface both run on the same thread.  If you want to provide form updates during a long-running process (like for a custom progress meter form), you need to explicitly give the UI a chance to update itself.

That's why you usually call DoEvents from inside of a loop.  The DoEvents call tells the code to wait its turn while the user interface is updated.  Here's what that looks like in practice:

Download the sample database to play around with this code.

Too Much of a Good Thing

Too many calls to the DoEvents function will slow down your code.  

A better approach is to call it only enough to stave off the "(Not Responding)" treatment from Windows.  

The exact frequency depends on how long it takes your code to execute each time through the loop.  The technique, though, is the same: use the Mod operator to call the DoEvents function every x times through a loop.

Dim i As Long
For i = 1 To 5000
    'Some slow code
    
    'Call DoEvents on every hundredth trip through the loop
    If i Mod 100 = 0 Then DoEvents
Next i

The goal is to leave no more than a 3-4 second gap between calls to DoEvents.  Anything longer than five seconds and Windows will flag a process as "Not Responding."  

Before and after images of the title bar of an Access application that's stopped responding.

Bringing an axe to a knife fight

The problem with the DoEvents function is that it's a blunt instrument.  The user interface is not the only thing that takes a turn when you call DoEvents.  Every message in the Windows Message queue gets processed, too.

The following excerpt from Chapter 13 of Advanced Visual Basic 6 by Matthew Curland is instructive:

"Visual Basic provides the DoEvents function for arbitrarily clearing pending messages in all the current thread's windows. Since pending methods masquerade as messages, calling DoEvents lets them through. The problem is that DoEvents lets everything else through as well.  You get a flood instead of a trickle."

I'll talk about the Windows Message queue and the nitty-gritty details of the DoEvents function in later articles in this series.  For now, the important point I want to make is that you usually don't want to call the DoEvents function on every single trip through a loop.  

Sometimes all you need is a knife

In certain situations, the Sleep API function can be used in place of DoEvents.

Sleep function
Suspends the execution of the current thread until the time-out interval elapses.

A value of zero causes the thread to relinquish the remainder of its time slice to any other thread  that is ready to run.
Do 
    'Some slow code
    
    'Give non-Access threads a chance to execute
    Sleep 0
Loop
Sleep 0 helps your code play nicely with other applications, but doesn't help within Access itself (e.g., updating the UI, processing Access events, etc.)

Unfortunately, there's not really another simple way to cede control of the processor so that Access can handle other events and update the user interface.  So, if you want to update the Access UI during a long-running process, you're pretty much stuck with DoEvents.


External references

Mod operator
Sleep function (synchapi.h) - Win32 apps
Suspends the execution of the current thread until the time-out interval elapses.

Article references

Demystifying DoEvents
The DoEvents function may be the single most misunderstood piece of code in the entire VBA language. In this series of articles, I’ll try to remedy that.
How to Use the DoEvents Function: A Demonstration
The demonstration and downloadable sample code in this article make it easy to understand how DoEvents works.

Photo by Hans-Jurgen Mager on Unsplash