home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
thread2.zip
/
README
< prev
next >
Wrap
Text File
|
1993-09-07
|
12KB
|
307 lines
MULTI-THREADING PM APPLICATIONS
Over the years, many different styles have emerged to
multi-thread PM applications. One approach is to start a new
thread whenever the application is asked to perform a lengthy
task. Another approach is to start one thread when the
application starts, then give it work to do.
What is really important is that your PM applications ARE
multi-threaded. The best ones are.
The style put forth in this article is the start-a-thread,
keep-it-around, and give-it-work approach. This approach has its
merits. Such a thread is blocked in the kernel scheduler, and it
does not incur any system overhead as it waits for work to do.
The start-one-when-needed approach is more costly, for
there is a non-trivial overhead in the kernel to create a thread.
The problem to solve is this: how best to harness a work-horse
thread?
One approach is to set up a pair of semaphores: one for signaling
the thread to start a task, and the other for the thread to
signal completion of the task. Another approach might be to use
the OS/2 queueing subsystem, the queue calls, in QUECALLS.DLL in
order to pass around messages.
The approach presented in this article is based on PM message
queues. It is a reasonable choice because PM applications are
required to have message queues already. Furthermore, it is
possible to achieve multi-threading without the use of
semaphores.
WHY MULTI-THREADED PM APPLICATIONS ARE A MUST
PM apps, like all OS/2 applications, call into the operating
system for services via the OS/2 API's. However, PM apps are
required to provide functions for the Presentation Manager to
call, too. These functions are called window procedures.
PM delivers messages to a PM application by calling its window
procedures. These messages include menu selections, mouse
clicks, and termination notification messages. PM sends a
message to the application when it is time to repaint the window.
PM can also deliver user-defined messages.
In order for the Presentation Manager to stay in synch with all
the applications on the desktop, it delivers some messages one at
a time. These are "sent" messages. Stated differently, when the
Presentation Manager sends a message to your window procedure, it
stops sending messages elsewhere until your procedure returns.
Therefore, PM apps must respond to sent messages in a timely
manner; one-tenth second is an often-cited response time that has
come to be known as the one-tenth-second rule. When an
application takes too long, unacceptable behavior occurs:
. user unable to minimize the application
. user unable to switch to another application
. hourglass pointer over entire desktop
. the system dialog: "Application is not responding to system
requests. Press Enter to end it."
Use a second thread in PM apps to bust the 1/10th second rule and
perform tasks that take time: diskcopy, upload/download, file
input/output, SQL queries. This article describes a simple,
robust two-threaded PM application architechure. Applications
coded in this style obey the 1/10th second rule, and they can
perform lengthy tasks.
THREAD DUTIES
Thread one is responsible for presentation. Thread one operates
the first message queue created by the application. This message
queue gets and dispatches messages to one or more application
windows on the desktop such as the client window, dialog windows,
and message boxes. It receives messages from menus and child
controls like buttons and listboxes. It processes messages
generated by the frame window controls. Thread one is devoted to
the operation of all the application's visible windows on the
desktop.
Thread two will create and operate another message queue. This
message queue will deliver messages to an object window and its
window procedure. The PM-defined window HWND_OBJECT is both the
parent and owner window of the application's object window.
Object windows are invisible; they do not appear on the desktop.
Best of all, they are not bound by the 1/10th-second rule!
When object window procedures execute on thread two, they are
perfect for doing time-consuming tasks. While the object window
is busy working on a task, the main window procedure is still
getting and dispatching messages in a timely manner -- as it
must.
POST A MESSAGE, DO SOME WORK
Thread one creates thread two, and thread two creates its own
message queue for its object window. Then thread two blocks in
WinGetMsg in the message loop (see OBJECT.C) until there is work
to do.
Scenario: the user selects a task from a pull-down menu. PM
sends a menu message to the client window procedure on thread
one. Thread one calls WinPostMsg and posts a user-defined
message to the object window on thread two. Thread two 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 | | | |
--------------- --------------- |
|
\ /
.
There is a convention used with the two message parameters of
WinPostMsg when posting a user-defined work message to the object
window: message parameter one is the window handle of the
originating window. By having the window handle of the
originator, the object window will know which window to
acknowledge upon completion of the task.
The sample code in APP.C only shows the client window procedure
originating tasks, but dialog boxes can certainly originate
tasks, too.
DISABLE WHEN BUSY
In the sample code, the application disables its client window
and selected menu items, then posts a message to the object
window to perform the lengthy task. This prevents the user from
initiating another work item while the object window is busy.
Note: when the client window is disabled, 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 is processing messages as it should.
The frame window is not disabled; therefore, the user can size or
minimize the frame window and/or switch to another application
while the application is busy.
Notice that 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 the
normal pointer. The application keeps a busy flag and references
it during processing of WM_MOUSEMOVE in the client window
procedure.
This is a simplistic (if not rigid) approach, but it is one that
can be modified by the application programmer: the programmer
chooses which items are grayed on which menus. If a given object
window (there could be many) was responsible for tasks A and B,
then the programmer would gray items A and B while the object
window was busy working on either A or B.
OBJECT WINDOW ACKNOWLEDGES COMPLETION
Upon completion of the lengthy task, the object window posts a
user acknowledgement message to the window that originated the
task. This informs the originating window that the task is
complete, and it can re-enable itself and menu items as required.
There is a convention used with message parameters on the
acknowledgement 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.
TERMINATION CONSIDERATIONS
The act of terminating (closing) the application is like a chain
reaction between the two threads. Usually, client windows post
themselves a quit message upon receipt of a close message. Not
here!
When this client window receives a close message, it posts a quit
message to the object window, then returns. When the object
window receives the quit message, it leaves the message loop,
posts a quit message back to the client, cleans up, then exits.
When the client window on thread one gets the quit message, it
leaves its message loop, cleans up, waits for thread two to exit,
then exits itself (and the process).
Note that the object window thread calls WinCancelShutdown. This
tells PM not to send a close message to the object window message
queue if the user shuts down the system while the application is
running. PM will send a close message to the client window
message queue, and the chain reaction begins.
COMMON DATA
Both threads share a common data space. This space is defined by
the GLOBALS structure in app.h. WM_CREATE processing allocates
this space and passes the pointer to thread two 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 per
window is set by the WinRegisterClass calls. In this sample,
both the client and object windows have four extra bytes of
window words by virtue of the last parameter to WinRegisterClass.
Dialog box procedures must obtain the pointer to shared memory
and store it in their window words when they initialize. 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, the programmer
can assert that a Boolean expression is true. At runtime, nothing
happens if the expression is true. If 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 the macro to
be a "no operation" once the application is debugged. In the C
language tradition, this is accomplished by defining the symbol
NDEBUG. This approach to program building yields "debug" and
"retail" versions of your program. See pmassert.h in the sample
code.
SIMILARITIES TO OTHER SAMPLE PROGRAMS
By now you may have recognized this architecture from the OS/2
Toolkit Print Sample (PRTSAMP.EXE). It is the same approach to
threading PM applications.