home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / thread2.zip / README < prev    next >
Text File  |  1993-09-07  |  12KB  |  307 lines

  1.  
  2. MULTI-THREADING PM APPLICATIONS
  3.  
  4. Over the years, many different styles have emerged to
  5. multi-thread PM applications.  One approach is to start a new
  6. thread whenever the application is asked to perform a lengthy
  7. task.  Another approach is to start one thread when the
  8. application starts, then give it work to do.
  9.  
  10. What is really important is that your PM applications ARE
  11. multi-threaded.  The best ones are.
  12.  
  13. The style put forth in this article is the start-a-thread,
  14. keep-it-around, and give-it-work approach.  This approach has its
  15. merits.  Such a thread is blocked in the kernel scheduler, and it
  16. does not incur any system overhead as it waits for work to do.
  17. The start-one-when-needed approach is more costly, for
  18. there is a non-trivial overhead in the kernel to create a thread.
  19.  
  20. The problem to solve is this:  how best to harness a work-horse
  21. thread?
  22.  
  23. One approach is to set up a pair of semaphores: one for signaling
  24. the thread to start a task, and the other for the thread to
  25. signal completion of the task.  Another approach might be to use
  26. the OS/2 queueing subsystem, the queue calls, in QUECALLS.DLL in
  27. order to pass around messages.
  28.  
  29. The approach presented in this article is based on PM message
  30. queues.  It is a reasonable choice because PM applications are
  31. required to have message queues already.  Furthermore, it is
  32. possible to achieve multi-threading without the use of
  33. semaphores.
  34.  
  35.  
  36.  
  37. WHY MULTI-THREADED PM APPLICATIONS ARE A MUST
  38.  
  39. PM apps, like all OS/2 applications, call into the operating
  40. system for services via the OS/2 API's.  However, PM apps are
  41. required to provide functions for the Presentation Manager to
  42. call, too.  These functions are called window procedures.
  43.  
  44. PM delivers messages to a PM application by calling its window
  45. procedures.  These messages include menu selections, mouse
  46. clicks, and termination notification messages.  PM sends a
  47. message to the application when it is time to repaint the window.
  48. PM can also deliver user-defined messages.
  49.  
  50. In order for the Presentation Manager to stay in synch with all
  51. the applications on the desktop, it delivers some messages one at
  52. a time.  These are "sent" messages.  Stated differently, when the
  53. Presentation Manager sends a message to your window procedure, it
  54. stops sending messages elsewhere until your procedure returns.
  55.  
  56. Therefore, PM apps must respond to sent messages in a timely
  57. manner; one-tenth second is an often-cited response time that has
  58. come to be known as the one-tenth-second rule.  When an
  59. application takes too long, unacceptable behavior occurs:
  60.  
  61.  
  62. . user unable to minimize the application
  63.  
  64. . user unable to switch to another application
  65.  
  66. . hourglass pointer over entire desktop
  67.  
  68. . the system dialog: "Application is not responding to system
  69.   requests.  Press Enter to end it."
  70.  
  71.  
  72. Use a second thread in PM apps to bust the 1/10th second rule and
  73. perform tasks that take time:  diskcopy, upload/download, file
  74. input/output, SQL queries.  This article describes a simple,
  75. robust two-threaded PM application architechure.  Applications
  76. coded in this style obey the 1/10th second rule, and they can
  77. perform lengthy tasks.
  78.  
  79.  
  80. THREAD DUTIES
  81.  
  82. Thread one is responsible for presentation.  Thread one operates
  83. the first message queue created by the application.  This message
  84. queue gets and dispatches messages to one or more application
  85. windows on the desktop such as the client window, dialog windows,
  86. and message boxes.  It receives messages from menus and child
  87. controls like buttons and listboxes.  It processes messages
  88. generated by the frame window controls.  Thread one is devoted to
  89. the operation of all the application's visible windows on the
  90. desktop.
  91.  
  92. Thread two will create and operate another message queue.  This
  93. message queue will deliver messages to an object window and its
  94. window procedure.  The PM-defined window HWND_OBJECT is both the
  95. parent and owner window of the application's object window.
  96. Object windows are invisible; they do not appear on the desktop.
  97. Best of all, they are not bound by the 1/10th-second rule!
  98.  
  99. When object window procedures execute on thread two, they are
  100. perfect for doing time-consuming tasks.  While the object window
  101. is busy working on a task, the main window procedure is still
  102. getting and dispatching messages in a timely manner -- as it
  103. must.
  104.  
  105.  
  106.  
  107. POST A MESSAGE, DO SOME WORK
  108.  
  109. Thread one creates thread two, and thread two creates its own
  110. message queue for its object window.  Then thread two blocks in
  111. WinGetMsg in the message loop (see OBJECT.C) until there is work
  112. to do.
  113.  
  114. Scenario:  the user selects a task from a pull-down menu.  PM
  115. sends a menu message to the client window procedure on thread
  116. one.  Thread one calls WinPostMsg and posts a user-defined
  117. message to the object window on thread two.  Thread two performs
  118. the task in the object window procedure.  When the task is
  119. complete, thread two posts an acknowledgement back to the
  120. originating window.
  121.  
  122.  
  123.  
  124.  
  125. client/dialog                         object window
  126. window on thread 1                    on thread 2
  127. ---------------                       ---------------      |
  128. |             |                       | waiting in  |      |
  129. | user        | WinPostMsg(           | WinGetMsg   |      |
  130. | requests    |     hwndObject,       |             |      |
  131. | a lengthy   |     WM_USER_WORKITEM, |             |      |
  132. | workitem    |     hwndToAck,        |             |      |
  133. |             |     null )            |             |      |
  134. |             | --------------------> |             |
  135. | window      |                       | perform     |     time
  136. | disabled    |                       | lengthy     |      |
  137. | while       |                       | task        |      |
  138. | busy        |                       |             |      |
  139. |             | WinPostMsg(           |             |      |
  140. |             |    hwndToAck,         |             |      |
  141. |             |    WM_USER_ACK,       |             |      |
  142. |             |    WM_USER_WORKITEM,  | task        |      |
  143. |             |    result code )      | complete    |      |
  144. |             | <-------------------- |             |      |
  145. | enable      |                       |             |      |
  146. | again       |                       |             |      |
  147. ---------------                       ---------------      |
  148.                                                            |
  149.                                                           \ /
  150.                                                            .
  151.  
  152.  
  153.  
  154. There is a convention used with the two message parameters of
  155. WinPostMsg when posting a user-defined work message to the object
  156. window:  message parameter one is the window handle of the
  157. originating window.  By having the window handle of the
  158. originator, the object window will know which window to
  159. acknowledge upon completion of the task.
  160.  
  161. The sample code in APP.C only shows the client window procedure
  162. originating tasks, but dialog boxes can certainly originate
  163. tasks, too.
  164.  
  165.  
  166.  
  167.  
  168.  
  169. DISABLE WHEN BUSY
  170.  
  171. In the sample code, the application disables its client window
  172. and selected menu items, then posts a message to the object
  173. window to perform the lengthy task.  This prevents the user from
  174. initiating another work item while the object window is busy.
  175.  
  176. Note:  when the client window is disabled, its message queue does
  177. not stop working.  Click on the client window while the object
  178. window is busy.  The beep you hear is proof that the client
  179. window's message queue is processing messages as it should.
  180.  
  181. The frame window is not disabled; therefore, the user can size or
  182. minimize the frame window and/or switch to another application
  183. while the application is busy.
  184.  
  185. Notice that the mouse pointer changes to an hourglass when it
  186. passes over the window of the busy application.  When the mouse
  187. pointer leaves the application window, it changes back to the
  188. normal pointer.  The application keeps a busy flag and references
  189. it during processing of WM_MOUSEMOVE in the client window
  190. procedure.
  191.  
  192. This is a simplistic (if not rigid) approach, but it is one that
  193. can be modified by the application programmer:  the programmer
  194. chooses which items are grayed on which menus.  If a given object
  195. window (there could be many) was responsible for tasks A and B,
  196. then the programmer would gray items A and B while the object
  197. window was busy working on either A or B.
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206.  
  207.  
  208.  
  209.  
  210.  
  211. OBJECT WINDOW ACKNOWLEDGES COMPLETION
  212.  
  213. Upon completion of the lengthy task, the object window posts a
  214. user acknowledgement message to the window that originated the
  215. task.  This informs the originating window that the task is
  216. complete, and it can re-enable itself and menu items as required.
  217.  
  218. There is a convention used with message parameters on the
  219. acknowledgement message:  message parameter one is the
  220. user-defined message posted to the object window.  With this
  221. parameter, the originating window can discern which activity is
  222. now complete.  The second message parameter is a result code.
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230. TERMINATION CONSIDERATIONS
  231.  
  232. The act of terminating (closing) the application is like a chain
  233. reaction between the two threads.  Usually, client windows post
  234. themselves a quit message upon receipt of a close message.  Not
  235. here!
  236.  
  237. When this client window receives a close message, it posts a quit
  238. message to the object window, then returns.  When the object
  239. window receives the quit message, it leaves the message loop,
  240. posts a quit message back to the client, cleans up, then exits.
  241. When the client window on thread one gets the quit message, it
  242. leaves its message loop, cleans up, waits for thread two to exit,
  243. then exits itself (and the process).
  244.  
  245. Note that the object window thread calls WinCancelShutdown.  This
  246. tells PM not to send a close message to the object window message
  247. queue if the user shuts down the system while the application is
  248. running.  PM will send a close message to the client window
  249. message queue, and the chain reaction begins.
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260. COMMON DATA
  261.  
  262. Both threads share a common data space.  This space is defined by
  263. the GLOBALS structure in app.h.  WM_CREATE processing allocates
  264. this space and passes the pointer to thread two on the call to
  265. _beginthread.
  266.  
  267. The client and object window procedures keep a pointer to this
  268. memory in their window words.  The number of extra words per
  269. window is set by the WinRegisterClass calls.  In this sample,
  270. both the client and object windows have four extra bytes of
  271. window words by virtue of the last parameter to WinRegisterClass.
  272.  
  273. Dialog box procedures must obtain the pointer to shared memory
  274. and store it in their window words when they initialize.  By
  275. default, dialog boxes have enough window words to hold a
  276. 32-bits-long pointer.
  277.  
  278.  
  279.  
  280.  
  281.  
  282. PMASSERT
  283.  
  284. The pmassert macro is a debugging tool.  It works much like the C
  285. language assert macro.  Anywhere in the source code, the programmer
  286. can assert that a Boolean expression is true.  At runtime, nothing
  287. happens if the expression is true.  If false, the macro displays the
  288. failed assertion in a message box along with the line number and the C
  289. source file name where the assertion failed.
  290.  
  291. Because pmassert is a macro, it is easy to redefine the macro to
  292. be a "no operation" once the application is debugged.  In the C
  293. language tradition, this is accomplished by defining the symbol
  294. NDEBUG.  This approach to program building yields "debug" and
  295. "retail" versions of your program.  See pmassert.h in the sample
  296. code.
  297.  
  298.  
  299.  
  300.  
  301. SIMILARITIES TO OTHER SAMPLE PROGRAMS
  302.  
  303. By now you may have recognized this architecture from the OS/2
  304. Toolkit Print Sample (PRTSAMP.EXE).  It is the same approach to
  305. threading PM applications.
  306.  
  307.