Are any of the windows in your PowerBuilder applications “haunted” by ghosts?
In the Windows operating system, the term “ghosting” is used to describe how the Desktop Window Manager visually intervenes on a user’s behalf whenever Windows believes the active, or "top-level" application window has become unresponsive. This article describes how Windows determines if a window should be ghosted and how the appearance of a window changes when it becomes ghosted. The article will also examine the common causes of an unresponsive PowerBuilder window and discuss some options for detecting, recovering from and even preventing windows with long-running processes from being ghosted.
Let’s start where most things begin… at the beginning – with an overview of the role of messages and message queues in an event-driven operating system.
Event Messages and the Message Queue – Where It All Begins
In an event-driven operating system, each application responds to events initiated by the user, by other applications, by the hardware, or by the operating system via a software mechanism called event messages. In Windows, an event message is a small data structure. Windows maintains two types of message queues:
- A system message queue.
- A thread-specific message queue for each GUI thread.
To avoid unnecessary overhead, all threads are initially created without a message queue, but one can be created if needed.
Whenever the user interacts with computer system hardware (some examples are moving the mouse, clicking a mouse button, or pressing a key on the keyboard), a device driver for that hardware converts the user’s input into messages and places those messages on the system message queue. The operating system removes the messages, one at a time, from the system message queue, examines them to determine the destination window, and then posts them to the message queue of the thread that created the destination window.
Every class of window contains a default window procedure – a function that performs the default processing of all messages sent or posted to windows of the class. The term “window class” in the context of the Windows ecosystem does not necessarily translate into what you may think of in PowerBuilder as a “window”. Command buttons, for example, are one example of a window class, as are single-line edit controls, and list boxes, etc.
Subclassing is a technique that allows an application to intercept and process messages before the class’s default window procedure has a chance to process them. This allows any subclassed window the option to:
- Pass the message to the original window procedure.
- Modify the message and pass it to the original window procedure.
- Process the message and not pass it to the original window procedure.
The thread removes messages from its queue and directs the system to send each message to the appropriate window procedure for processing.
Big Brother (the Windows O/S) Is Always Watching
An important component of thread management is ensuring that all GUI threads are responding to queued messages in a timely manner. Messages are internally timestamped, so Windows frequently checks the timestamps of all queued messages to look for any that have been in queue for too long. In this era of multi-core, multi-gigahertz processors, the term “too long” translates to five seconds… which is nearly an eternity! When Windows finds a queued message for a top-level (i.e., active) window that has reached the ripe old age of five seconds, it notifies the Desktop Window Manager (DWM) task that the window has become unresponsive.
Note: The five-second responsiveness threshold in Windows is not configurable.
The Desktop Window Manager Performs a Responsiveness Intervention
When the DWM learns of an unresponsive top-level window, it intervenes on the user’s behalf by performing the following steps:
- It takes a snapshot of, then hides the unresponsive window.
- Using the image it just took, the DWM replaces the unresponsive window with a "ghost" window that has the same Z-order, location, size, and visual attributes, except the phrase "(Not Responding)" is appended to the window's title/caption and the "X" (cancel) button is enabled. Figure 1 below shows an example of a window that has the title "A Simple, Unresponsive Window That Is..." in this unresponsive, "ghost" state.
Figure 1: An example of an unresponsive, "ghosted' window.
Ghosting gives a user an opportunity to move the (ghost) window, minimize it (but only if the "real" window can be minimized), or even close the application. Note that the user is actually interacting with the ghost window that was created by the DWM and not the real application window, since the actual window is not currently processing any event messages.
Prior to adding ghosting functionality to Windows (in Windows XP?), the user had to be savvy and knowledgeable enough to manually start the Windows Task Manager, locate the application with the unresponsive window in the Task Manager window, and end the task in order to close an unresponsive application. Frankly, that is beyond the skillset of the majority of Windows users.
- Once the ghost window is displayed, if the user clicks in the client area of the ghost window, the window is then rendered mostly opaque, giving the ghost window a true "ghostly" appearance. See Figure 2 for an example of a window in this "second stage" of ghosting.
Figure 2: An example of the same unresponsive window that has now become opaque, or more "ghost-like".
If a ghosted window resumes processing its backed-up messages in the thread’s message queue, Windows restores the real application window to the desktop while reflecting any change to either the ghost window’s location or size that was made by the user.
What Can Make a Window Unresponsive?
We’ve learned that Windows considers a window to be unresponsive when the window stops processing event messages for five seconds or more. Several factors can negatively influence the responsiveness of a window. For example:
- A long-running (more than five seconds) process/task. The task does not have to be solely compute-intensive; it can perform file I/O operations, interact with a database server, etc. It only has to keep the execution thread busy enough to prevent it from returning to the operating system to process the next queued message.
- A request to a database that results in a lengthy (more than five seconds) wait before a response is received.
- The execution of a command, utility or another application using the same execution thread, which causes the PB application to wait for completion or a response for longer than five seconds.
- A call to a function in an external Dynamic Link Library (DLL), such as a Windows API call or a function in a third-party DLL, that results in a lengthy delay before it completes.
This list is by no means complete, but you can see that, in general, there are two factors that can adversely affect the responsiveness of a window: Long-running tasks and lengthy wait times.
There’s No Simple Solution When Long Wait Times Are Involved
Nearly all PowerBuilder applications utilize a single-threaded execution model in which all processing is performed by a single thread. The relative simplicity of the single-threaded execution model is attractive because it is easy to code and easy to manage. An application perceived by its user to be responsive is able to quickly and efficiently manage the graphical user interface (mouse clicks, keyboard input, etc.)
The major disadvantage of the single-threaded execution model is that responsiveness can suffer whenever the application does anything other than manage the user interface, such as do some (any) work. When this happens, not only can the user get impatient, so too can Windows!
As mentioned earlier, long wait times are often directly related to something outside of the application’s control, such as waiting for the database server to return the result set from a query. The most effective way to improve responsiveness when long wait times are involved is to employ multi-threaded execution. By executing a task with a potentially long wait time in a separate thread, the user interface (i.e., “main”) thread can continue to quickly and efficiently process messages after it has created and started the new execution thread.
Since version 5, PowerBuilder has provided a means of implementing multi-threaded execution via shared objects (these are different from variables having shared scope, also known as “shared variables”), but coding for and using shared objects in PowerBuilder to avoid long waits is an advanced topic that is beyond the scope (no pun intended) of this article. A discussion of multi-threaded execution in PowerBuilder will have to wait (OK, that pun was intentional).
Is there anything that can be done to eliminate or reduce the likelihood of ghosting in “normal”, i.e., single-threaded PowerBuilder applications? The answer is yes, so let’s examine some possibilities.
The Simple, Yet Draconian Solution: Turn Ghosting Off
The easiest way to prevent Windows from considering a top-level window as unresponsive is to tell Windows to stop checking for unresponsive windows. The word “draconian” means rigorous, unusually severe or heavy-handed, and disabling the ghosting feature certainly meets that definition, in my opinion.
It’s easy to turn ghosting off. A simple Windows API function does the trick. Here’s the PowerBuilder external function declaration (EFD):
SUBROUTINE DisableProcessWindowsGhosting ( &
) LIBRARY "user32.dll"
Once the EFD has been saved in a window or user object, simply call that function from within the object:
DisableProcessWindowsGhosting()
This Windows API function can be used in either 32-bit or 64-bit PB applications.
There are some important caveats you should be made aware of before you call this API function:
- Ghosting is disabled only for the process (application) that invokes the API function. No other applications are affected, and the disabling effect goes away when the requesting process/application ends. Thus, if you exit and restart the application, ghosting is once again enabled.
- There is no API function or other means to re-enable ghosting functionality in an application once it has been disabled, other than by exiting and restarting the application.
- Once ghosting has been disabled for a process/application, the only means by which a user can subsequently force the unresponsive application to end is to utilize the “old school, pre-ghosting” method:
- Press Ctrl + Alt + Delete.
- Start the Windows Task Manager.
- Locate the application task listed in the Task Manager window.
- End the task.
When ghosting has been disabled for an application, Windows will no longer identify an unresponsive window in the application by appending “(Not Responding)” to the window’s title, so it will be less obvious to the user that the window has become unresponsive.
If your customers/users and management are OK with these limitations, then this particular solution is simple to code and to implement. Alternative solutions exist, however, so we'll examine those next.
Two Alternative Approaches: Reactive vs. Proactive
For long-running tasks, there are two alternative approaches to managing window unresponsiveness:
- Reactive – Detect after the fact when Windows has classified the active window as unresponsive and take action(s) to make Windows believe the window is once again responsive.
This is a slightly easier approach to code, but the user may see the window switch back and forth between normal and “Not Responding”, depending how quickly the window detects that it has become unresponsive. - Proactive – Take action(s) at least every five seconds that will prevent Windows from classifying the window as unresponsive.
This approach can potentially, but does not have to be a little more difficult to code, but if done well, the user will not be subjected to seeing the window jump back and forth between unresponsive and responsive (normal).
The Reactive Approach – Detecting When A Window Has Become Unresponsive
Windows provides an API function that an application can use to tell if a window has been classified as unresponsive. Here is an example PB EFD for the IsHungAppWindow API function:
FUNCTION Boolean IsHungAppWindow ( &
Longptr hWnd &
) LIBRARY "user32.dll" ALIAS FOR “ThisWindowIsUnresponsive”
The IsHungAppWindow API function takes the handle to the window to be checked as its argument. The handle to the window is obtained by using the PowerScript Handle function. In this example, I’ve optionally specified an alias for the API function name in order to improve the readability of the code where it is used. If this EFD is placed in a window, then the following code in one of the window’s event scripts or in a window function will detect if Windows has categorized this window as unresponsive:
If ThisWindowIsUnresponsive(Handle(This)) Then
.
. (Take action to make the window responsive here)
.
End If
What action can you or should you code when you find out the window is unresponsive? Let’s look at a traditional solution and also at an unconventional, and perhaps better solution.
The Yield PowerScript Function
From the PB Help topic for the Yield PowerScript function:
… Yield checks the message queue and if there are messages in the queue, it pulls them from the queue.
At first glance, this sounds ideal! And… well, it almost is.
If the Yield PowerScript function is called when there are no queued event messages, the function does nothing. There’s very little overhead and the function executes quickly, so the task being performed in the window will resume and the app will chug merrily along. Now let’s carefully examine what happens when there are queued messages.
The PB Help says queued messages are “pulled from the queue.” This means the queued event messages are removed from the queue, then sent to the window’s window procedure and processed. There are two potential issues that can arise from doing this:
- You, as the developer, have no way to control what happens. The event message could be harmless, such as “Move the window”, or "Paint (redraw) the window", or more importantly, it might initiate an action which could delay and/or interfere with completion of the task being performed, such as closing the window. This is always a risk when Yield is used.
The “Reactive Test” tab page of the “Not Responding” sample application, which is covered later, shows how the execution of a process can be interrupted by another script within the same application whenever the Yield PowerScript function is used. - If your code has changed the default "arrow" pointer, say, to Hourglass to indicate to the user that work is being performed (a nice feature to incorporate in your application), the pointer will be restored to its previous setting if another script in the same application is executed.
This is certainly not a major issue (rest assured, no empires will crumble), but your app’s users may notice this has happened and report it as a bug. Of course, you can prevent this by always resetting the pointer to Hourglass immediately after a Yield, but only if you know that you need to do this.
If neither of these potential issues are a show-stopper, then feel free to use the Yield PowerScript function as a reactive solution to a window that has become unresponsive.
An Alternative to the Yield Function
What if there was an alternative to using the Yield function that effectively let Windows know that your application is still being responsive, without any of the potential issues that can arise from using Yield?
Well, there is such an alternative available. If used carefully, the Windows API function PeekMessageW can do the trick.
The PeekMessageW API function requires the use of two structures: The Windows POINT and MSG structures. Here are sample PB structure definitions for each, as they would appear if defined as object structures within a window’s or user object’s exported source:
type os_point from structure
long l_x
long l_y
end type
type os_msg from structure
longptr lptr_hwnd
unsignedlong ul_message
unsignedlong ul_wparam
unsignedlong ul_lparam
unsignedlong ul_time
os_point str_pt
unsignedlong ul_lprivate
end type
I’ve prefaced the names of these structures here with the prefix “os_”, for “object structure”. Each or both can be stand-alone structure objects, if desired, and the name of these structures can be any valid PowerBuilder object name. I’ve used Hungarian notation for the names of the structure members, which you are not required to do, of course.
Please note the “msg” structure contains a member that is a “point” structure.
Before we look at code, we also need the EFD for the PeekMessageW API function. Here’s one example:
FUNCTION Boolean PeekMessage ( &
REF os_msg lpMsg, &
Longptr hWnd, &
UnsignedLong wMsgFilterMin, &
UnsignedLong wMsgFilterMax, &
UnsignedLong wRemoveMsg &
) LIBRARY "user32.dll" ALIAS FOR "PeekMessageW"
An external function declaration is not executable… it is only a declaration to the compiler. It exists to tell PowerBuilder about the details of how to call an entry point inside a DLL that is external to PowerBuilder. Since the EFD is not executable, the names you assign to the argument values are immaterial – they are only placeholders included for syntactical convenience. Even though the names in an EFD are not important, I prefer to use the same names that are used in the Windows API documentation for consistency with the documentation.
Note: The online documentation for the Windows API is where you can find out the name of the Windows DLL where the API function resides. Look near the bottom of the web page of an API function for this information. Links to the documentation for the Windows API functions called out in this tutorial are listed in the Links section at the end of the document.
Whenever I use an API function that has a Unicode-specific version (where the function name ends with an uppercase “W”, as it does in this case), I prefer to define the name of the API function without the Unicode designation character and then include the ALIAS FOR clause, since it is understood (or should be understood) that PowerBuilder uses Unicode internally.
Note: You do not have to use the ALIAS FOR clause if you specify the name of the external function exactly as it is defined (in mixed case) in Windows.
Argument Values for the PeekMessageW API Function
The first argument to this API function is an output argument. It is a pointer to a Windows MSG structure. By using the REF keyword in the EFD, PowerBuilder will pass the MSG structure to the API function by reference (via a pointer, conveniently), which satisfies the requirements for this argument. If an event message is queued, Windows will populate the MSG structure members.
The second argument is the handle to a window. This can either be populated with the return value from the PowerScript Handle function, or it can be NULL (zero).
Please note that NULL in this context is different from a null PowerBuilder variable… In the Windows API, NULL means the value is zero. When NULL is passed as the second argument to the PeekMessageW API function, this tells Windows to examine the thread’s message queue for any message, instead of only thread messages directed to the window having the specified handle. Since PowerBuilder applications are by default single-threaded, we can safely specify zero (NULL) for the second argument value if the application is single-threaded.
I’ve used the PB Longptr data type for the window handle argument so that the proper amount of information is passed on the execution call stack regardless of whether the app is executed in 32-bit or in 64-bit. A “handle” in Windows is a four-byte integer in a 32-bit process and an eight-byte integer in a 64-bit process. PowerBuilder translates the Longptr data type into a Long or a LongLong at compile time, so it is the ideal data type to use for any Windows handle.
For our needs, we do not require any message filtering, so the third and fourth arguments (wMsgFilterMin and wMsgFilterMax) will have a value of zero.
The last argument (wRemoveMsg) has only three possible values, which are defined by the following constants:
Constant ULong PM_NOREMOVE = 0 // Do not remove the queued msg
Constant ULong PM_REMOVE = 1 // Remove the queued msg
Constant ULong PM_NOYIELD = 2 // Don’t release thread about to go idle
These are the same names assigned to these constants in the Windows API. The “PM_” prefix in these names is used to identify these constants as being used by the PeekMessageA/W (“A” for ANSI and “W” for “Wide” Unicode) functions. We do not wish to disturb the content of the thread’s message queue by removing any messages, so we’ll use PM_NOREMOVE (zero) for the last argument value.
The PeekMessageW API function returns a Boolean; True if a queued message is found/returned and False if no messages are queued.
How to Use the PeekMessageW API Function as an Alternative to Yield
Below is the pertinent source code for an object function you can place in a window or non-visual object. I call this function of_TellWindowsImResponding:
// Function of_TellWindowsImResponding() returns (none)
Constant ULong PM_NOREMOVE = 0 // Do not remove the queued msg
os_msg lstr_msg // Windows MSG structure
PeekMessage(lstr_msg,0,0,0,PM_NOREMOVE)
Using the argument values shown, the PeekMessageW API function retrieves the first queued message (if any exist) on the thread’s message queue without removing it. For our purposes, you do not want to remove any queued messages, because we want all event messages to eventually be examined and processed. If there are no queued messages when PeekMessageW is called, no action is taken.
Very Important: The little-known reason the PeekMessageW API function can change the unresponsive status of a window is simple: It causes Windows to refresh the internal timestamp of the queued message without (in this case) removing the message from the thread’s message queue.
After executing this object function, the first (the oldest) message in the thread’s message queue is no longer “expired”, or more than five seconds old, so Windows considers the top-level window of the application to once again be responsive; In order to restore the contents of the desktop from this change in the window’s responsiveness, the DWM destroys the ghost window and restores the real application window to the desktop in its original Z-order. The application now has another five seconds to complete its current task before it becomes unresponsive once again. When used in this manner, the PeekMessageW API function is very efficient, so little overhead is spent.
In my opinion, this is a much better alternative than using the Yield PowerScript function, because it executes quickly and there are no potential deleterious side effects.
The Proactive Approach
Instead of waiting for Windows to classify the window as unresponsive before reacting, the proactive approach attempts to prevent Windows from detecting the window as unresponsive in the first place. As we’ll soon see, the tools available to us are largely the same (the Yield PowerScript function and the PeekMessageW API function). What would be very helpful for proactive management of unresponsiveness is a means of keeping track of elapsed time at sub-second resolution. Helpful, but not required.
Why Managing Elapsed Time Is Not Absolutely Necessary
If you have a long-running, repetitive process, it is not a hard and fast requirement that you manage the time that has elapsed since the process started or since the last action to prevent unresponsiveness was performed… you can, for example, execute the PeekMessageW API function or the Yield PowerScript function during every iteration or several times within a single iteration. If each iteration takes about one to five seconds, then it may make sense to do this without monitoring elapsed time. However, if the loop iterates 1,000 times per second, then such a high frequency of preventive actions is largely wasteful and very likely will needlessly slow the task that is in progress.
It’s a good thing we can conveniently set and use a window timer in PB, isn’t it? Well… not really, in this particular case, as we'll see next.
The Problem with Using Window Timers Under These Circumstances
When a window timer fires, an event message (Message ID: WM_TIMER) is generated, which ends up in the thread’s message queue. Normally, this is not a problem when the active window is responsive. In this case, however, the delayed processing of thread event messages by an unresponsive window means a timer event will be “stuck” in the message queue backlog. Therefore, in this situation, window timers cannot be used to notify a window that a nearly five-second interval has expired.
Another means of accurately tracking elapsed time is needed.
The PowerBuilder Time Datatype
The PB Time datatype has a resolution of 0.000001 seconds (one microsecond), but PB does not provide any functions for determining time intervals of less than one second. Ideally, we would like to be able to determine elapsed time at a resolution of around one-tenth of a second or less. This would make it easier to determine when the time interval is approaching five seconds.
Fortunately, it is relatively easy to create time calculation functions that work with the fractional portion of a second.
The n_timecalc Non-Visual Object
A PowerBuilder non-visual object (NVO) has been created to perform sub-second calculations based on PB Time values. The n_timecalc object included in the sample application contains functions that perform the following types of time calculations:
- Add specified hours, minutes, seconds and microseconds to a Time value.
- Calculate the sum of Time values and return a Time value.
- Subtract specified hours, minutes, seconds and microseconds from a Time value.
- Calculate the difference between Time values and return a Time value.
- Calculate the difference between Time values in microseconds.
- Calculate the time elapsed between starting and ending Time values.
It’s All in the Timing
Regardless of whether you decide to manage the responsiveness of a window using a reactive or proactive approach, arguably the most critical component is the timing or frequency of how often actions are taken. It makes sense, then, to benchmark the performance of the long-running processes/tasks within an application so that you can make intelligent decisions about how many iterations of a loop should be performed before a responsiveness test (for reactive) or a preventive message queue timestamp reset (for proactive) needs to be performed. The n_timecalc NVO can help you collect this information.
With either approach, the application must perform one or more actions at semi-regular intervals. Does this mean every time a process iterates? Not necessarily, but it could… it could also mean at multiple points within a single iteration. It really depends on the amount of time it takes for the process to complete a typical iteration. You should also take into account the variability in performance between various PC’s.
Since a reactive approach takes action only after the window has been classified as unresponsive, there is little point in checking before five seconds has elapsed.
For example, if each iteration takes one second on average, then you might wish to check during each iteration or only after every five or six iterations. However, if each iteration takes approximately 0.2 seconds, you may wish to check for unresponsiveness only after twenty-five iterations.
If each iteration takes longer than five seconds, say, ten seconds, for example, then Windows will be placing the window in unresponsive status repeatedly, so you should probably change to a proactive approach and take preventive steps at multiple points through each iteration, if this is possible.
If you wish to proactively prevent Windows from categorizing the window as unresponsive, you need to take preventive action before five seconds has elapsed.
For example, if a process takes (on average) one second per iteration, you might want to take action every three or four iterations.
The “Not Responding” Sample Application and “Ghosting Buster" Non-Visual Object
A simple, single-window sample application is available for download in the PowerBuilder section of the CodeXchange area of the Appeon Community web site. The app is called “Not Responding”, and it illustrates reactive and proactive approaches to handling an unresponsive window that runs a compute-intensive loop of configurable duration and conditions.
The sample application includes the n_ghostingbuster NVO, which contains functions that help manage an unresponsive window and invokes the Window API functions that have been described in this document. It also contains the n_timecalc NVO mentioned earlier to assist with elapsed time management.
The sample application’s window is organized into two tab pages: “Reactive Test” and “Proactive Test”. Configuration of the compute-intensive test/process loop, optional actions the window can perform while a test is running, and the action the window is to take at each “checkpoint” during the test are nearly identical for the reactive and proactive tests.
The Reactive Test Tab Page
The “Reactive Test” tab page appears as shown below in Figure 3:
Figure 3: The “Reactive Test” tab page of the “Not Responding” sample application.
Use the options on the left side to configure the testing loop and checkpoint conditions, as well as the action the window will take if it finds itself to be unresponsive during a checkpoint. These values include:
- Total number of iterations to be performed, in millions.
This value can range from 25 to 500 (million). The test/process loop iterates extremely fast, since it does very little work. Your mileage may vary, but I have found that 25 million iterations will take approximately one to ten seconds, depending on the performance of the PC running the application. I suggest your first test be limited to 25 million iterations so that you can get a feel for how long a larger iteration limit will take. - Number of iterations before checkpoints begin, in millions.
This value can range from 10 to 100 (million). Regular checks for unresponsiveness will begin once this threshold has been reached. This permits the window to become unresponsive before the first checkpoint occurs, if this is the behavior you wish to test. - Number of iterations between checkpoints, in thousands.
The value can range from 5 thousand to 2,000 thousand (2 million). The window will perform a responsiveness test each time the loop iterates this many times, once the checkpoint threshold has been reached.
The window can optionally display a time-of-day clock that normally updates each second, except when a test is running (it stops updating because the thread stops processing event messages.) If the window is configured to issue a Yield when the window discovers it is unresponsive, you will see the clock update when the Yield PowerScript function is executed. The clock resumes normal operation once the test has completed. You can alternatively choose to start a timer event in the window that performs no action. You can put code in the window’s timer event, if desired, but the timer event will execute regardless because the script is not empty (it contains a comment).
The test can also can also configure what action, if any, is taken when each checkpoint occurs. You can have the window unconditionally perform a Yield at each checkpoint or run the “I am responding” function in the n_ghostingbuster NVO (which executes the PeekMessageW API function). Alternatively, the window can first test if has become unresponsive at each checkpoint and if so, either take no action, issue the Yield PowerScript function, or execute the “I am responding” (PeekMessageW) function.
The window’s background color changes whenever a test is running.
Once a test has completed, statistics collected during the test are displayed on the right side of the tab page.
Below the statistics is a button labeled “Click Me Only When This Window Is Unresponsive”. The button is enabled only (a) when the test is configured to check window unresponsiveness and (b) when a Yield is to be issued when the window finds itself to be unresponsive. When a test configured in this manner is running, clicking this button will (eventually) interrupt the test to display a message box. This illustrates how the Yield PowerScript function permits other actions to be performed, in this case delaying the completion of the test/process loop.
The Proactive Test Tab Page
The “Proactive Test” automatically performs either a Yield or executes the “I am responding” (PeekMessageW) function at a fairly regular interval in order to “inoculate” the window from becoming unresponsive for up to five seconds at a time. The “Proactive Test” tab page is shown below in Figure 4:
Figure 4: The “Proactive Test” tab page of the “Not Responding” sample application with sample results.
The configuration of the proactive test is similar to the reactive test, except for a couple of differences:
- No checkpoint threshold exists because the proactive approach is based on elapsed time. Therefore, the amount of elapsed time between preventive inoculations is configurable.
This value can range from 1.0 to 4.9 seconds. - The option to perform no action has been removed from the Proactive Test, as this would defeat the entire purpose of the proactive test. If you wish to run a test that performs no actions, this can be configured on the “Reactive Test” tab page.
As is done for the reactive test, statistics are also collected during the proactive test and the results are displayed once the test concludes. The proactive test results include a count of the number of times that preventive action was taken during the test, and a DataWindow shows the elapsed time between each inoculation that was measured while the test was running. Figure 4 shows an example of the post-test statistics.
A Final Word About Both Approaches
If you examine the code in the Clicked event for the "Start" button on either tab page, you'll see that the "Close" button is disabled while a test is running because I did not want you to close the sample application accidentally. If a test is configured to use the Yield PowerScript function, it would be possible to accidentally close the application by clicking the Close button when the window is unresponsive.
Alternatively, I could have checked in the window's CloseQuery event if a test is actively running when the user attempts to close the window and if so, ignored the close request. Instead, I've left this as an exercise for the reader. You can see what happens if you click on the window's "X" button (in the upper-right corner) immediately after starting a test that is configured to use the Yield PowerScript function, but only if you click it before the window is ghosted.
Summary
The “Not Responding” sample application illustrates coding techniques to reactively and proactively manage a window that performs a long-running process which, if left unchecked, would cause Windows to flag the window as unresponsive and thereby cause the window to be “ghosted”.
When long wait times are not the cause of unresponsiveness, the Yield PowerScript function or the PeekMessageW Windows API function can be used to either prevent or recover from an unresponsive condition. Using the Yield PowerScript function can lead to side effects that may delay completion of a long-running process, whereas the PeekMessageW API function does not have any known side effects when used with the “Do not remove the message from the queue” option.
Two non-visual objects, n_ghostingbuster and n_timecalc, contain functions you may find helpful in either detecting an unresponsive window and preventing or recovering from unresponsiveness.
Window unresponsiveness caused by long wait times (i.e., greater than five seconds) can best be addressed by spawning secondary execution threads to perform the task(s) where these long wait times occur. It is the author’s intent to create a follow-on tutorial in the near future about PowerBuilder shared objects that will be accompanied with an example application which demonstrates how shared objects can be used to offload tasks from the application’s main (GUI) thread.
Links
Here are some links to Microsoft documentation related to unresponsive applications:
- Preventing Hangs in Windows Applications:
https://docs.microsoft.com/en-us/windows/win32/win7appqual/preventing-hangs-in-windows-applications - Using Messages and Message Queues:
https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues - DisableProcessWindowsGhosting function:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-disableprocesswindowsghosting - PeekMessageW function:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew - IsHungAppWindow function:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-ishungappwindow
Comments (2)