The application programmer's view
This section looks at the impact of the input polling method on application code.
When programming with OSF/Motif, or other modern window systems, application programmers writing simple applications typically do not worry much about the details of the input model. They simply define the the widgets that make up their application windows, register the appropriate callbacks, or equivalent, for the user actions of interest, and implement the code for these callbacks. Although this is ideally true, one important aspect is sometimes not considered: because callbacks are run by the same process that is running the user interface, all of the code that is run by the callback (an operation) is run before the application returns to polling. Thus, when long-running operations are run, application responsiveness, and whole system responsiveness on some operating systems is affected.
In Common Widgets, programmers construct applications using the standard OSF/Motif model described above. However, additional facilities are provided to allow long running operations to execute without impacting responsiveness.
Because VA Smalltalk provides support for multiple, priority-scheduled threads of control, or processes, operations that do not use user interface facilities can be run by a separate, low priority process, called a background process. The user interface remains responsive because it is higher priority than the background process and is activated whenever input is available.
Unfortunately, OSF/Motif itself does not use a multithreaded model and thus it is not possible for multiple application processes to concurrently execute user interface code, for example, redrawing a widget, performing an individual graphical operation, or reading an event. Some kind of synchronization is necessary.
It is possible for each application to implement its own synchronization scheme that prevents background processes from attempting to concurrently execute user interface code. However, VA Smalltalk provides standard mechanisms to support this kind of synchronization.
Background user-interface requests
A background user interface request is a block of code that must be run by the UIProcess at a point when no other user interface code is being run. The following two methods are then implemented in class CwAppContext to take background user interface requests.
Tip:
A background (non-UI) process must never make widget or graphics requests directly--it must use syncExecInUI: or asyncExecInUI: to ask the user interface to issue the request on its behalf.
Background user interface requests are run by atomically interleaving their execution with the event and callback processing of the user interface process. Once execution of the block has been started, no further user interface events can be processed until execution of the block has been completed.
asyncExecInUI
The asyncExecInUI UIProcess executes a block during readAndDispatch processing, at the next "clean" point, that is, at the next point where it is not already executing user interface code. No result is returned.
Processes with higher priority than the UIProcess do not block when this method is run. In this case, aBlock is run the next time the UI becomes active and reaches a clean point. Processes at the same or lower priority than the UIProcess normally block until aBlock has been run, but this is not guaranteed.
The process that runs this message is not provided any indication of when aBlock has been run. In particular, it is not guaranteed that aBlock has been run when the method returns.
If this message is sent by code that is executing in the UIProcess, then aBlock is run after all previously queued background user interface requests have been run.
Tip:
A background process can reawaken the UIProcess and cause a context switch (because the UIProcess is higher priority) by executing: CwAppContext default asyncExecInUI: ]
syncExecInUI:
The syncExecInUI: UIProcess runs a block during readAndDispatch processing, at the next "clean" point, that is, at the next point where it is not already executing user interface code. The result of evaluating aBlock is returned.
Execution of the process that evaluates this method always suspends until aBlock has been evaluated.
If this message is sent by code that is executing in the UIProcess, the block is run immediately.
Both of these methods are implemented to add the code to be run, aBlock, to a collection of background user interface requests maintained by the CwAppContext. This collection is FIFO ordered so operations can use syncExecInUI: to ensure that all previously sent asyncExecInUI: messages have been processed.
After adding the request, both of the above methods reactivate the UIProcess if it was sleeping. As was described in the previous section, the CwAppContext processes the background user interface requests as part of the normal functioning of the readAndDispatch method. Processing of pending background user interface requests is interleaved with user interface event dispatching.
Using the above methods, it is possible for programmers to construct applications that are both responsive and contain long running operations. The long-running operations are run by background tasks that use asyncExecInUI: or syncExecInUI: to access the user interface. Obviously, the programmer is responsible for ensuring that the individual blocks that are passed to the asyncExecInUI: methods do not take an unusually long time to execute.
Tip:
Because execution of background graphics requests can be deferred while events are processed, background user interface request blocks must be prepared for eventualities such as widgets being destroyed between the request being posted and run.
Work procs and timer procs
In addition to background user interface requests, there are two other mechanisms that can be used to defer execution or have a method repeatedly executed. The following two methods are in the class CwAppContext. Unlike the background user interface request methods described above, work procs and timer procs can only be registered by the UI process itself. As with background user interface requests, work procs and timer procs are sent from the event loop during idle processing.
The processing of active work procs and timer procs prevents the user interface process from suspending, and background processes will not run until all work procs and timer procs have been removed. However, the enableProcs: message in CwAppContext can be used to temporarily disable work proc and timer proc processing in order to allow the UIProcess to suspend.
As with background user interface requests, work procs and timer procs are only processed by the UIProcess during readAndDispatch processing. If the UIProcess is compute bound, no work procs or timer procs will be processed until the UIProcess returns to the event loop.
Note:
Keep the amount of work done in a work proc or timer proc to a minimum. If they are too long, they keep the UIProcess from returning to the event loop, which can cause user interface responsiveness to degrade. Long-running operations should be coded in background tasks that use asyncExecInUI: or syncExecInUI: to access the user interface.
addWorkProc:receiver:selector:clientData:
This message adds a work proc for idle UI processing, and returns an object which uniquely identifies the work proc. This object can later be used to remove the work proc using removeWorkProc:. The 1-parameter message defined by selector is repeatedly sent to receiver (with clientData as the parameter) while the event loop is idle waiting for events. Multiple work procs may be added, and the last one added is called first. If the work proc message returns true, then it is automatically removed and will not be called again.
addTimeOut:receiver:selector:clientData:
This message adds a timer proc which is sent after the specified number of milliseconds have elapsed, and returns an object which uniquely identifies the timer proc. As with work procs, this object can be used later to remove the timer proc before it expires if it is no longer needed. Multiple timer procs can be added, but unlike work procs, timer procs are always sent only once. When the interval expires, the 1-parameter message specified by selector is sent to receiver with clientData as parameter.
Note:
Because timer procs are only sent when the UI is idle, they may not expire at the exact time specified. They are only guaranteed to expire after the given number of milliseconds have passed.
The UI idle processing mechanisms are as follows:
asyncExecInUI:
syncExecInUI:
addWorkProc:...
addTimerProc:...
Can be registered by any process
Can be registered by any process
Can be registered only by UI process
Can be registered only by UI process
Next pending block evaluated at next UI idle
Block evaluated immediately if called from UI process--otherwise next pending block evaluated at next UI idle
Next pending message sent at next UI idle
Next expired message sent at next UI idle
No return value
Returns result of block
Returns workProc ID
Returns timerProc ID
Block evaluated once only
Block evaluated once only
Message sent repeatedly until it returns true
Message sent once only
Processed in FIFO order
Processed in FIFO order
Processed in LIFO order
Processed in LIFO order
Cannot be disabled
Cannot be disabled
Can be disabled
Can be disabled
 
Last modified date: 04/20/2020