Ever since true threading showed up in version 6, Clarion has been able to spawn background tasks that don't open windows but just get some job done. That ability is both a blessing and a curse.
...
Below is a screen shot of the sample program I'll be discussing in this article. I've started two tasks on separate threads, each of which has now window and only a simple loop of 100 repetitions with a sleep statement so it all doesn't go by in a blink of an eye.
Cross-thread messaging
The example program makes use of a class called DCL_UI_BackgroundProgressDisplay, and which is now part of The DevRoadmaps Clarion Library (DCL).
...
Similarly, Notification:
Note |
---|
The NOTIFICATION function is used by a receiving thread. It receives the notification code, thread number, and parameter passed by the sending thread’s NOTIFY statement. NOTIFICATION returns FALSE (0) if the EVENT() function returns an event other than EVENT:Notify. If EVENT:Notify is posted, NOTIFICATION returns TRUE. Because calls to NOTIFY and NOTIFICATION are asynchronous, the sender thread can be closed already when receiver thread accepts the EVENT:Notify event. |
So my background threads can use Notify to tell the UI thread it needs to redisplay some progress information. And I can update that progress information via an object I share between the UI and the background threads.
Here's the code for my Main UI procedure:
Code Block |
---|
Main procedure
Window WINDOW('Background task progress bars'),AT(,,364,104),GRAY,FONT('Microsof' & |
't Sans Serif',8)
BUTTON('Start task 1'),AT(15,19,51,19),USE(?StartTask1),ICON(ICON:None)
STRING('Message'),AT(86,19,253),USE(?Message1)
PROGRESS,AT(86,32,253,6),USE(?PROGRESS1),RANGE(0,100)
BUTTON('Start task 2'),AT(15,57,51,19),USE(?StartTask2),ICON(ICON:None)
STRING('Message'),AT(86,57,253,10),USE(?Message2)
PROGRESS,AT(86,71,253,6),USE(?PROGRESS2),RANGE(0,100)
END
ProgressDisplay1 DCL_UI_BackgroundProgressDisplay
ProgressDisplay2 DCL_UI_BackgroundProgressDisplay
code
open(window)
ProgressDisplay1.SetProgressControlFEQ(?progress1)
ProgressDisplay1.SetStringControlFEQ(?Message1)
ProgressDisplay2.SetProgressControlFEQ(?progress2)
ProgressDisplay2.SetStringControlFEQ(?Message2)
accept
case event()
of EVENT:OpenWindow
ProgressDisplay1.Enable()
ProgressDisplay2.Enable()
end
case accepted()
of ?StartTask1
start(Task,,address(ProgressDisplay1))
of ?StartTask2
start(Task,,address(ProgressDisplay2))
end
end
close(window)
|
...
Other than that, the Task procedure's code is unremarkable. It simply contains two method calls, one to set the string control's value and another to set the progress control's value. Neither of these methods updates the control directly, however.
The DCL_UI_BackgroundProgressDisplay class
Here's the class declaration:
...
- The SetProgressControlValue and SetStringControlValue calls happen on the background thread. They save values and send notifications to the UI thread.
- TakeEvent executes on the UI thread and receives notifications and displays those values in the UI.
Problems and other funky issues
This class has its issues, however. First, if you click either button before its task is finished, a second task starts and also updates the progress and string controls, with much flickering ensuing. On the bright side this shows the robustness of the class - it doesn't break when used by more than one background thread. But really this kind of behavior should be controllable.
...