A High-Level Overview of the DoEvents Function Call

Why does calling DoEvents save us from the dreaded "(Not Responding)" treatment? It's all about handling "Windows Messages."

A High-Level Overview of the DoEvents Function Call

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

Why It Works: The High-Level Overview

The DoEvents function, "Yields execution so that the operating system can process other events."  

Since Access is both event-based and single-threaded, you can run into a situation where some code gets stuck waiting for other code to finish.  For example, while a loop is running, the user could be clicking their mouse and pressing keys on the keyboard.  Each of those actions raises an event.  Those events wait to be handled until the loop finishes–or until you explicitly call the DoEvents function to handle them.

At the operating system level, these "events" are implemented as Windows Messages.

The Windows Message Queue

Here's a brief description of the Windows Message queue:

The Message Loop

An application will receive thousands of messages while it runs. (Consider that every keystroke and mouse-button click generates a message.) Additionally, an application can have several windows, each with its own window procedure. How does the program receive all these messages and deliver them to the correct window procedure? The application needs a loop to retrieve the messages and dispatch them to the correct windows.

For each thread that creates a window, the operating system creates a queue for window messages. This queue holds messages for all the windows that are created on that thread. The queue itself is hidden from your program. You cannot manipulate the queue directly. However, you can pull a message from the queue by calling the GetMessage function.

Of course, the text above is not intended for Access developers.  Part of what makes Access a rapid application development tool is that it hides much of this lower-level plumbing from view.  Instead of dealing with Windows Messages and the Message queue directly, Access abstracts away the low-level details and provides us with form and control events, which are easier to handle.

As Access programmers, though, we can't ignore this underlying complexity entirely.  Sometimes the abstraction leaks and we need to know how to deal with it.

What's a "Windows Message"?

Windows Messages are used at a low level to control many of the operations of a modern GUI application.  Most of them are recognizable in C++ source code as predefined constants of the form WM_{ACTIONNAME}, such as WM_SETCURSOR, WM_SETTEXT, WM_COPY, WM_KEYDOWN, etc.

The system passes input to a window procedure in the form of a message. Messages are generated by both the system and applications. The system generates a message at each input event—for example, when the user types, moves the mouse, or clicks a control such as a scroll bar.

As Access developers, we don't need to deal with the Windows Message queue directly.  We still need to be aware of it, though.  Every Windows Message coming in and going out of our program runs through the message queue.  The fact that Access abstracts away the details of the message queue does not absolve us from understanding how and why it works.

Not Responding

If your VBA code hogs the processor for more than about five consecutive seconds, Windows will change the status of the msaccess.exe process from "Running" to "Not Responding."  You can see this change in Task Manager's Status column.  Additionally, the Access window will dim and the Title Bar caption will get the text "(Not Responding)" appended.

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

Here's the explanation of this phenomenon from Microsoft:

If a top-level window stops responding to messages for more than several seconds, the system considers the window to be not responding. In this case, the system hides the window and replaces it with a ghost window that has the same Z order, location, size, and visual attributes. This allows the user to move it, resize it, or even close the application. However, these are the only actions available because the application is actually not responding.

DoEvents to the rescue

Here's the key line from the above excerpt: "If a top-level window stops responding to messasges for more than several seconds, the system considers the window to be not responding."  To be clear, Microsoft Access itself is the "top-level window."

The reason your applications sometimes show "(Not Responding)" is because there are messages from Windows sitting on the Access message queue waiting to be answered.  As long as you are executing VBA code, though, these Windows messages just keep piling up on the queue.

When you call DoEvents, you are basically saying to your code, "Hey, take a break while I deal with all these Windows Messages that have been piling up."  Once all the Windows Messages are handled, the DoEvents function is complete, and your code can continue processing.

Note that calling DoEvents is an all-or-nothing proposition.  When you call DoEvents, it will process every single item on the message queue.  This is relatively slow.  You wouldn't want to call DoEvents on every single iteration of a loop, for instance.

There is no separate "GUI Thread"

Microsoft Access is a single-threaded application.  This means that when you execute a piece of code in VBA, that code occupies all of Access's available processing power.

Many Windows applications have a dedicated graphical user interface (GUI) thread.  In these applications, the business logic runs in a separate thread.  This approach allows the user interface to remain responsive even as the application performs processor-intensive tasks.

In Access, on the other hand, the user interface runs within the same thread as the VBA code.  This means that a long-running VBA process can completely block any updates to the user interface.

If all you want to do is update a form's display without handling every message on the queue, you can call the form's Repaint method.

External references

DoEvents Function
The function, method, object, or property described in this topic is disabled if the Microsoft Jet Expression Service is running in sandbox mode, which prevents the evaluation of potentially unsafe expressions.
Window Messages (Get Started with Win32 and C++) - Win32 apps
Window Messages (Get Started with Win32 and C++)
The Law of Leaky Abstractions
There’s a key piece of magic in the engineering of the Internet which you rely on every single day. It happens in the TCP protocol, one of the fundamental building blocks of the Internet. TCP…
About Messages and Message Queues - Win32 apps
This section discusses Windows messages and message queues.
Form.Repaint method (Access)

Referenced articles

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.

Image by Pexels from Pixabay

All original code samples by Mike Wolfe are licensed under CC BY 4.0