2
votes

I'm working on a program the uses an EXEC command to run a make file. This can take a long time so I want to put it in the background so the GUI doesn't lock up. However I also want the GUI to be disabled and a progress bar to run only while the make file is compiling.

So how can I detect when a background progress has finished in TCL?

Edit: It gets more complicated because my boss wants the command window to stay open (or be visable) so the user can see the progress of the make and see if it errors.

P.S. Would figuring out threading be easier? I need some way to prevent the GUI from locking up (prevent NOT RESPONDING).'

Edit: The GUI is made with TK. I think TK is single-threaded which causes the problem. Or it could be that it defaults to single threaded and I want to set it to multi-thread.

2

2 Answers

3
votes

As @glenn-jackman pointed out, the use of fileevent is preferred (because it should work everywhere).

proc handle_bgexec {callback chan} {
    append ::bgexec_data($chan) [read $chan]
    if {[eof $chan]} {
        # end of file, call the callback
        {*}$callback $::bgexec_data($chan)
        unset ::bgexec_data($chan)
    }
}

proc bgexec {callback args} {
    set chan [open "| $args" r]
    fconfigure $chan -blocking false
    fileevent $chan readable [list handle_bgexec $callback $chan]
    return
}

Invoke this as bgexec job_done cmd /c start /wait cmd /c make all-all. job_done gets called with the output of the command after it finishes.

It is also possible to use threads for this things, but this requires a threaded tcl build (which is now default for all platforms AFAIK, but older versions of Tcl esp. under unix don't build a threaded Tcl by default.) and the Thread package (which is included by default). An approach to use it with Threads would be:

thread::create "[list exec cmd /c start /wait cmd /c make all-all];[list thread::send [thread::id] {callback code}];thread::exit"

If you need to call this on a regular basis it might be worth to use only one worker thread instead of creating a new one for each job.

Edit: Add /wait as parameter for start the keep the first cmd running.

cmd /c start /wait cmd /c make all-all
0
votes

You want to run the make process in a pipeline and use the event loop and fileevent to monitor its progress (see http://wiki.tcl.tk/880)

proc handle_make_output {chan} {
    # The channel is readable; try to read it.
    set status [catch { gets $chan line } result]
    if { $status != 0 } {
        # Error on the channel
        puts "error reading $chan: $result"
        set ::DONE 2
    } elseif { $result >= 0 } {
        # Successfully read the channel
        puts "got: $line"
    } elseif { [chan eof $chan] } {
        # End of file on the channel
        puts "end of file"
        set ::DONE 1
    } elseif { [chan blocked $chan] } {
        # Read blocked.  Just return
    } else {
        # Something else
        puts "can't happen"
        set ::DONE 3
    }
}

set chan [open "|make" r]
chan configure $chan -blocking false
chan event $chan readable [list handle_make_output $chan]
vwait ::DONE
close $chan

I'm not certain about the use of vwait within Tk's event loop. Perhaps an expert will help me out here.