Multithreading Presentation Manager Applications

By Monte Copeland

Over the years, many different styles have emerged to multithread Presentation Manager (PM) applications.

The approach presented in this article is the start-a-thread, keep-it-around, and give-it-work approach and is based on PM message queues. It is a good choice, because PM applications must have message queues already. Additionally, this choice lets you achieve multithreading without using semaphores.

Why Multithreaded PM Applications Are a Must
PM applications, like all OS/2 applications, call into the operating system for services using the OS/2 APIs. However, PM applications must provide functions, called window procedures, for PM to call.

PM delivers messages to a PM application by calling window procedures. For example, these messages include menu selections, mouse clicks, and termination-notification messages. In addition, PM sends a message to the application when it is time to repaint the window or it can deliver user-defined messages.

For PM to stay synchronized with all the applications on the desktop, it delivers messages one at a time. These are sent messages. In other words, when PM sends a message to your window procedure, it stops sending messages elsewhere until your procedure returns.

Therefore, PM applications must respond to all sent messages in a timely manner. The often-cited response time is 1/10th second; hence, the 1/10th-second rule. When an application takes too long to respond to messages, unacceptable behavior can occur.

This article describes a robust, two-threaded PM application architecture. Applications coded in this style obey the 1/10th second rule, and yet, can perform long tasks. A second thread in PM applications is created to bust the 1/10th-second rule, and perform long tasks, such as diskcopy, upload/download, file input/output, or SQL queries.

Thread Responsibilities
Thread 1 is responsible for presentation. It operates the first message queue that the application creates. This message queue gets and dispatches messages to one or more application windows on the desktop.

Thread 1 presents the client window, as well as the dialog and message boxes. It reacts to command messages from menus and child controls. It processes messages that the frame-window controls generate. In fact, thread 1 is devoted to the operation of all the application's visible windows on the desktop.

Thread 2 operates a second message queue that is created by thread 2 of the application. This message queue delivers messages to an object window and its window procedure. Object windows are invisible, that is, they do not appear on the desktop. More importantly, they are not bound by the 1/10th-second rule.

When object-window procedures run on thread 2, they are perfect for doing any time-consuming tasks required by the application. While the object window is busy working on a task, the main-window procedure continues to get and dispatch messages in a timely manner-as it must.

Post a Message, Do Some Work
Thread 1 creates thread 2, and thread 2 creates its own message queue for its object window. Then it blocks in the call to the API, WinGetMsg, in the message loop. (See the OBJECT.C file in the sample code that follows this article.)

The object window stays blocked until there is work to do. For example, a user selects a task from a pull-down menu. PM sends a menu message to the client window procedure on thread 1. Thread 1 calls the API, WinPostMsg, and posts a user-defined message to the object window on thread 2. Thread 2 performs the task in the object-window procedure. When the task is complete, thread two posts an acknowledgement back to the originating window.

client/dialog                        object window window on thread 1                   on thread 2 ---                      ---      | |             |                       | waiting in  |      | | user       | WinPostMsg(           | WinGetMsg   |      | | requests    |     hwndObject,       |             |      | | a lengthy   |     WM_USER_WORKITEM, |             |      | | workitem    |     hwndToAck,        |             |      | |             |     null )            |             |      | |            | > |             | | window      |                       | perform     |     time | disabled   |                       | lengthy     |      | | while      |                       | task        |      | | busy       |                       |             |      | |            | WinPostMsg(           |             |      | |             |    hwndToAck,         |             |      | |             |    WM_USER_ACK,       |             |      | |             |    WM_USER_WORKITEM,  | task        |      | |             |    result code )      | complete    |      | |            | < |             |      | | enable      |                       |             |      | | again      |                       |             |      | ---                      ---      |                                                            |                                                           \ /                                                            . The following convention exists with the two message parameters of the API, WinPostMsg, when posting a user-defined work message to the window:

Message parameter one is the window handle of the originating window.

By having the window handle of the originator, the object window knows which window to acknowledge when the task completes.

The sample file, APP.C, only shows the client-window procedure originating tasks; however, dialog boxes also can originate tasks.

Disable While Busy
In the sample code, the application disables its client and selected menu item, and then posts a message to the object window to perform the lengthy task. These actions prevent the user from initiating another work item, while the object window is busy. (See the message WM_USER_DISABLE_CLIENT in the file, APP.C, which is sent before the application posts the work request to the object window.)

Note: When the application disables the client window, its message queue does not stop working.

Click on the client window, while the object window is busy. The beep you hear is proof that the client window's message queue continues to process messages.

The frame window is not disabled; therefore, the user can minimize the frame window or switch to another application while the application is busy.

The mouse pointer changes to an hourglass when it passes over the window of the busy application. When the mouse pointer leaves the application window, it changes back to a normal pointer. The application keeps a busy flag and references it during processing of WM_MOUSEMOVE in the client-window procedure.

This approach is simplistic (if not rigid), but it is one that you can modify by choosing which items are grayed on which menus. If a given thread/object window pair (there could be many) was responsible for tasks A and B, you would gray items A and B, while the object window was busy working on either A or B.

Object Window Acknowledges Completion
When the lengthy task completes, the object window posts a user-acknowledgment message to the window that originated the task. This message informs the originating window that the task is complete, and that it can re-enable itself and any menu items, as required.

Use the following convention with message parameters on the acknowledgment message:

Message parameter one is the user-defined message posted to the object window.

With this parameter, the originating window can discern which activity is now complete; the second message parameter is a result code.

Closing Considerations
The act of closing the application is like a chain reaction between the two threads. Usually, client windows post a WM_QUIT message to themselves when they receive a WM_CLOSE message-not in multithreaded PM applications.

When the client window receives a WM_CLOSE message, it posts a WM_QUIT message to the object window, then it returns. When the object window receives the WM_QUIT message, it leaves its message loop, cleans up, posts a WM_QUIT message to thread 1. Thread 1 leaves its message loop, waits for thread 2 to exit, and then exits itself (and the process).

The object-window thread calls the API, WinCancelShutdown. If the user shuts down the system while the application is still running, this API tells PM not to send a WM_CLOSE message to the object-window message queue. PM sends a WM_CLOSE message to the client-window message queue, then the chain reaction starts.

Common Data
Both threads share a common data space. This space is defined by the GLOBALS structure in the app.h file. WM_CREATE processing allocates this space and passes the pointer to thread 2 on the call to _beginthread. The client- and object-window procedures keep a pointer to this memory in their window words. The number of extra words for each window is set by the API, WinRegisterClass. Both the client and object windows have 4 extra bytes of window words.

When dialog-box procedures initialize, they must obtain the pointer to shared memory, and store it in their window words. By default, dialog boxes have enough window words to hold a 32-bits-long pointer.

pmassert
The pmassert macro is a debugging tool. It works much like the C-language assert macro. Anywhere in the source code, you can assert that a Boolean expression is true. At runtime, nothing happens if the expression is true. If the expression is false, the macro displays the failed assertion in a message box along with the line number and the C source file name where the assertion failed.

Because pmassert is a macro, it is easy to redefine it to be a no operation, after you debug the application. In the C-language tradition, you accomplish this by defining the symbol, NDEBUG. This approach to program building produces both debug and ret ail versions of your program. See the pmassert.h file in the sample code.

Acknowledgments
By now you might have recognized this architecture from the OS/2 Toolkit Print Sample (PRTSAMP.EXE) on the CD-ROM. It is the same.

Sample Code
Use the following sample code to create your own multithreaded PM applications.