PURPOSE: Examine options for keeping a GUI up to date while doing something that is expected to take a long time. (also see [Countdown program]) ---- [KBK] - 19 April 2001 The single thread model that the [Tcl event loop] uses makes for simpler programming in most cases, because the Tcl programmer doesn't generally need to worry about thread safety. Most programmers with experience in dealing with threaded applications are happy to avoid the insidious bugs that result when the complex and subtle rules for threads are violated. Unfortunately, one place where threads really shine is where two components of an application compete for resources. Perhaps the commonest example that new Tcl programmers call to mind arises in dealing with long-running calculations. If the GUI is to be kept alive, then it must get some CPU resources, even if the long-running calculation doesn't relinquish them willingly. The easiest way to structure this sort of thing is to use another process, which you launch with the [open] command. You use a [fileevent] on the resulting pipe to monitor the process's output and do whatever you need to do in the GUI, and your user interface stays alive. ---- BM - 26 August 2004 That might be a problem when you want the exit code. However you can get around that on Linux using a compound command that echoes the exit code. Another possibility, surely, is to use send -async? ---- Sometimes, though, using another process isn't feasible (you may, for instance, need to be portable to Macintosh) or you have other reasons for wanting to keep your calculations in the same address space as your GUI. In this case, your best bet is to structure your code so that it does a little bit of work at a time. Let's work through some examples. The first example plots trajectories of a cannonball at various elevation angles of the cannon. Full code for the example is shown at the bottom of this page. A second, much simpler example, follows. Starting with the cannonball example, let's just note that the calculation begins by calling the [[sim::init]] procedure and that it does a small amount of work by calling [[sim::one_step]]. The [[sim::one_step]] procedure returns 1 if there is more work to be done and 0 if it's finished. We'll be plotting the results on a canvas, and we want to keep a set of cross-hairs on the canvas up to date at all times: proc update_crosshairs { w x y } { $w coords xhair $x 0 $x [winfo height $w] $w coords yhair 0 $y [winfo width $w] $y $w raise xhair $w raise yhair } grid [canvas .c -width 640 -height 480] .c create line 0 0 0 480 -tags xhair -fill white .c create line 0 0 640 0 -tags yhair -fill white bind .c {update_crosshairs %W %x %y} A naive Tcl programmer would then want to implement the long-running calculation using [[update]]: sim::init .c 640 480 while { [::sim::one_step] } { update } This approach has a number of pitfalls, which are discussed in [Update considered harmful] and [Tcl event loop]. The primary problem is unexpected recursion: the [[update]] command enters the event loop recursively, and it in turn may enter event handlers recursively. If the event handlers aren't expecting to be re-entrant, chaos will result. ---- What could go wrong in this specific example? -davidw ---- A far better practice is to return to the event loop between steps, using [[after idle]] to schedule the next calculation once the events settle down. Alas, simply doing this isn't safe! An 'after idle' that reschedules itself causes trouble, as the manual warns: At present it is not safe for an idle callback to reschedule itself continuously. This will interact badly with certain features of Tk that attempt to wait for all idle callbacks to complete. If you would like for an idle callback to reschedule itself continuously, it is better to use a timer handler with a zero timeout period. Even this warning is oversimplified. Simply scheduling a timer handler with a zero timeout period can mean that the event loop will never be idle, keeping other idle callbacks from firing. The truly safe approach combines both: proc doOneStep {} { if { [::sim::one_step] } { after idle [list after 0 doOneStep] } return } sim::init .c 640 480 doOneStep This skeleton should be considered the basic framework for performing long running calculations within a single Tcl interpreter. ---- For those who want to try this out, I include the complete program below. # We'll create a namespace that holds the state of the calculation at a given time: namespace eval sim { variable dt 0.2; # Integration step size variable v 98; # Muzzle velocity (m/s) variable g 9.8; # Acceleration of gravity variable stepNum; # Number of steps performed so far variable maxSteps; # Maximum number of steps needed variable canvas; # Path name of the canvas that # displays results variable xorig; # X coordinate of the cannon on canvas variable yorig; # Y coordinate of the cannon on canvas variable scale; # Scale factor (pixels per meter) } # The plotting can be initialized with a call to the 'sim::init' procedure: proc sim::init { w width height } { variable stepNum variable maxSteps variable dt variable v variable g variable canvas variable xorig variable yorig variable scale set stepNum 0 set maxSteps 101 set vy [expr { sqrt( 0.5 ) * $v }] set t [expr { $vy / $g }] set x [expr { 2 * $vy * $t }] set canvas $w set xorig [expr { $width / 2 }] set yorig [expr { $height * 0.95 }] set scale [expr { 0.45 * $width / $x }] return } # A single trajectory will be plotted each time the 'sim::one_step' procedure is called. This procedure will return 1 if there is still work to be done, or 0 if the last trajectory has been calculated. proc sim::one_step {} { variable stepNum variable maxSteps variable dt variable v variable g variable canvas variable xorig variable yorig variable scale if { $stepNum >= $maxSteps } { return 0 } # Run one simulation run set angle [expr { 3.14159 * $stepNum / ( $maxSteps - 1 ) }] set vx [expr { $v * cos($angle) }] set vy [expr { $v * sin($angle) }] set x 0.0 set y 0.0 set coords {} while { $y >= 0.0 } { set cx [expr { $scale * $x + $xorig }] set cy [expr { -$scale * $y + $yorig }] lappend coords $cx $cy set x [expr { $x + $vx * $dt }] set y [expr { $y + $vy * $dt }] set vy [expr { $vy - $g * $dt }] } eval [list $canvas create line] $coords incr stepNum return 1 } # The 'update_crosshairs' procedure keeps the cross-hairs on the canvas aligned with the mouse pointer. proc update_crosshairs { w x y } { $w coords xhair $x 0 $x [winfo height $w] $w coords yhair 0 $y [winfo width $w] $y $w raise xhair $w raise yhair } # The 'doOneStep' procedure is discussed above. proc doOneStep {} { if { [::sim::one_step] } { after idle [list after 0 doOneStep] } return } # The main program creates the canvas and the cross-hairs, and establishes bindings to keep the cross-hairs up to date. grid [canvas .c -width 640 -height 480] .c create line 0 0 0 480 -tags xhair -fill white .c create line 0 0 640 0 -tags yhair -fill white bind .c {update_crosshairs %W %x %y} # There are two alternative ways to manage switching between handling GUI events and doing a piece of calculation. The second one is preferred. # set naive 1; # to show the behavior of the naive implementation if { [info exists naive] } { sim::init .c 640 480 while { [::sim::one_step] } { update } } else { sim::init .c 640 480 doOneStep } ---- '''Simplified Example: Stopping a Long Calculation''' This very simple example illustrates the same principle as the cannonball example, but with much less code. The GUI is simply a label and start and stop buttons. A global variable stores the value displayed by the label. The "long running" calculation just increments the variable. # very simple GUI for long running calculation grid [label .l -textvariable count] grid [button .b1 -text start -command start] grid [button .b2 -text stop -command stop] set count 0 The calculation is performed one step at a time, as recommended in the earlier example. One approach uses a global variable as a flag which is checked as the calculation progresses. proc start { } { set ::running 1 doOneStep } proc stop {} { set ::running 0 } proc doOneStep {} { if { $::running } { incr ::count after idle [list after 0 doOneStep] } return } Another approach would be to stop the calculation by intervening directly into the event queue. At the time the stop routine is called, one of the two scripts "doOneStep" or "after 0 doOneStep" is in the queue. Cancelling the event stops the calculation. proc start { } { doOneStep } proc stop {} { foreach e [after info] { set script [lindex [after info $e] 0] if { $script == "doOneStep" || \ $script == "after 0 doOneStep"} { after cancel $e } } } proc doOneStep {} { incr ::count after idle "after 0 doOneStep" } ---- A different treatment of some of the same material appears in "Scripted wrappers for legacy applications, Part 2" [http://www.itworld.com/AppDev/4061/swol-0105-regex/]. ---- [[We should write up a [thread]ed example, at least for completeness. Threading *does* remain relevant for lots of database work, for instance.]] ---- [Category GUI]