third_party_content:community:commentary:revdevx:19723.5744907407

Yield or Die!

Published 30 DEC 2021 at 01:47:16PM

When executing a long running process in a desktop environment, such as selecting records from a large table, it is important for the user to be able to interact with the application even though it is busy. A common example of this is displaying a dialog box that shows the progress of an operation and allowing the user to cancel it if they wish. Failure to provide this ability generally results in user frustration, or causes the dreaded "Window Ghosting" effect where Windows changes a form's caption to "Not Responding" (this is never a good look, and usually ends in a quick visit to the Task Manager to kill the application).

In order to avoid this problem we have to allow the application to check for user interaction, a process usually referred to as "yielding" (hence the awful title of this post), and this time we'll take a look at the various options available to accomplish this and the differences between them. Before we go any further however, here's a little background information on how OpenInsight runs beneath the hood so that you can appreciate how messages and events are handled.

OpenInsight.exe (aka. the "Presentation Server", or "PS") has a main thread (the "UI thread") with a Windows message loop that manages all of the forms and controls, and it also has an internal "event queue" for storing Basic+ events that need to be executed. The PS also creates an instance of the RevEngine virtual machine ("the engine"), which has its own thread (the "engine thread") with a Windows message loop, and is responsible for executing Basic+ code.

When the PS needs to execute an event it passes it to the engine directly if possible, otherwise it adds the details to the event queue and then posts a message to itself so the queue can be checked and processed when the engine is not busy. When the engine receives the event data it is executed on the engine thread. Stored procedures such as Get_Property, Set_Property, and Exec_Method provide a way for the Basic+ event to communicate back to the PS to interact with the user interface controls and forms during its execution.

The key point to note here is that Basic+ event code runs in a different thread to the UI, so while the engine thread is processing the event, the UI thread is basically waiting for it to finish, and this means that it may or may not get chance to process it's own message loop. This is where the problems can begin, and why the need for a yielding ability, because:

  1. The engine thread needs to be paused or interrupted in some fashion so that the UI thread can check and process its own Windows message queue for things like mouse, keyboard and paint messages. If this queue is not checked at least every 10 seconds then Windows assumes that the PS is hung and the "Not Responding" captions are shown on the application forms.
  2. While the engine is processing an event, the PS cannot pass it a new one, so it is added to the event queue. If we are waiting to process some Basic+ event code like a button CLICK to cancel the current operation, then we need some way for this to be retrieved and executed before the current event is finished.

So, now we know why "window ghosting" happens we can take a look at the various options to deal with it.

This is a direct call to the Windows API Sleep function, and it puts the engine thread to sleep for at least the specified number of milliseconds. However, while calling this will allow Windows to schedule another thread to run, there's no guarantee that this would be the UI thread, so it's not really a good solution.

This is a simple wrapper around the Windows API Sleep function, with a sleep-time of 10ms. This suffers from the same disadvantages discussed for MSWin_Sleep above (This function remains for backwards compatibility with early versions of of OI and Windows).

This is a direct call to the Windows API SwitchToThread function which forces Windows to schedule another thread for execution. Like MSWin_Sleep and WinYield there's no guarantee that this would cause the UI thread to run, so again it's not a great solution.

This is a new method in version 10.1 that performs two tasks that solve the problem:

  1. It explicitly tells the UI thread to process its message queue (which will avoid the "ghosting" issue), and
  2. It allows the UI thread to process the event queue so waiting events can be executed as well.

One possible drawback here is that waiting events are also processed, and this might not be a desirable outcome depending on what you are doing. In this case there is another method called PROCESSWINMGS that should be used instead.

(FYI - The PROCESSEVENTS method is essentially a wrapper around the venerable Yield() stored procedure, but allows the same functionality to be called via the standard object-based method API rather than as a "flat" function. Yield() itself is still and will be fully supported in the product).

This is a new method in version 10.1 that tells the PS to process it's Windows message queue but it does not process any Basic+ events, i.e. it prevents the "ghosting" effect but does not cause events to fire before your application is ready for them.

Version 10.1 has added more functionality to help you avoid the dreaded "Not Responding" message via the PROCESSWINMGS and PROCESSEVENTS methods, and hopefully, armed with the information above, this will help you to write better integrated desktop-applications.

Comments

Original ID: revdevx.com/?p=3619
  • third_party_content/community/commentary/revdevx/19723.5744907407.txt
  • Last modified: 2024/01/29 20:23
  • by 127.0.0.1