Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Killing a running procedure at the lower level 1

Status
Not open for further replies.

firelex

Programmer
Jan 10, 2002
118
DE
Hello, all!
The problem is:
How could a running function be killed from the upper level?
Here is an example:

Code:
proc type_it {} {
global var
        toplevel .top
        set var ""
        label .top.lab1 -text "Enter your text here :"
        entry .top.ent -bg white -width 14 -textvariable var
        button .top.b1 -text "OK" -command "destroy .top"
        button .top.b2 -text "Cancel" -command "destroy .top"
        grid .top.ent -columnspan 2
        grid .top.b1 .top.b2
        tkwait window .top
        return $var
}

proc get_text {wid} {
        for {set i 0} {$i<10} {incr i} {
                $wid configure -text [type_it]
                update idletasks
        }
}

label .lab -text &quot;You have entered : &quot;
label .lab2 -text &quot;&quot;
button .but -text &quot;Enter something...&quot; -command &quot;get_text .lab2&quot;
grid .lab .lab2
grid .but -columnspan 2

Pressing the button starts a function that runs 10 times, asking each time for a new text, if you press &quot;OK&quot;.

The point is : how could I, after the &quot;Cancel&quot; is pressed kill the running function &quot;get_text&quot; (from the function-context of the function &quot;type_it&quot;) without killing the whole process? I mean so that the &quot;.&quot;-window didn´t disappear.
 
This one is more ulis and avia's area but
the new additions to trace should make it possible to trace the command/execution and fire a callback to kill the procedure.
 
Hmm... marsd made an interesting suggestion, but I don't think it's a path we should investigate. Although it might be fascinating to see if we could, the point is that we shouldn't.

(In case you're interested, I was musing as to whether we could do something weird with a variable trace that would force a break or return condition in the loop via return -code break or return -code return. But like I said, I'm not going there.)

In Tcl, there's no good way to force a tight loop like that to abort. But I don't think Tcl is unique in that; I can't think of a good way to write a C program or a Java program that could externally abort a loop.

If I step back and look at the larger question, I think what you're really asking is: &quot;How do you structure a sequence of actions that the user can abort at any step?&quot;

One relatively easy way in your case would be to modify your type_it procedure to return 2 peices of information: the characters typed and an Ok/Cancel indicator. Then your loop could test whether or not the user clicked Cancel, and if so terminate the loop. You've got several different ways to return 2 values from a procedure. Here are the ones that immediately occur to me:[ul][li]Return a 2-element list[/li][li]Return one (or more) of the values as global variables. in this case, you could already do this with the characters typed, as you already have var declared global.[/li][li]Interpret a return value of an empty string as meaning the user clicked Cancel. (Tk's built-in file selection dialogs take this approach.)[/li][li]Pass the name(s) of one or more variables as arguments to the procedure, use upvar to create aliases in the proper scope, and then store the return values in those variables.[/li][/ul]This type of approach is usually best if you want the user to go through a series of steps. Do the steps one at a time, allow the user to abort, test for the abort signal in your code, and handle it appropriately.

On the other hand, sometimes you have a series of events that need to occur without user input, but you want the user to be able to abort at any time. In this case, one of the best techniques in Tcl is to break down the activity into a series of steps, and then use the after command to run those steps in sequence. Let's take a look at an example:

Code:
# Increment the value of &quot;x&quot; once per second.
# &quot;x&quot; is used as a -textvariable for a label.

proc UpdateTimer {val} {
    global x timer
    set x $val
    incr val

    # Schedule the next call to UpdateTimer to
    # occur in 1 second. Pass the updated value
    # of &quot;var&quot; as the argument to that call.

    # Save the ID of the scheduled after event
    # in the global variable &quot;timer&quot;. We can
    # use this ID with the &quot;after cancel&quot;
    # command to cancel the pending event.

    set timer [after 1000 [list UpdateTimer $val]]
}

# Stop the timer by canceling the pending after
# event.

proc StopTimer {} {
    global x timer
    set x &quot;Stopped&quot;
    if {[info exists timer]} {
        after cancel $timer
        unset timer
    }
}

set x &quot;Stopped&quot;
label .value -textvariable x

button .start -text &quot;Start&quot; -command {
    .stop configure -state normal
    .start configure -state disabled

    # Start the timer running

    UpdateTimer 0
}

button .stop -text &quot;Stop&quot; -state disabled -command {
    .start configure -state normal
    .stop configure -state disabled

    # Cancel the timer

    StopTimer
}

pack .value -anchor w -padx 4 -pady 4
pack .start .stop -side left -padx 4 -pady 4

You can adapt this technique to a variety of tasks. For example, you might have an operation that requires many, many iterations and takes a long time to complete. Simply putting this into a loop will freeze your application's GUI while the calculation takes place. You could use update idletasks inside the loop to refresh the screen, but other events wouldn't be processed. You could use update inside the loop to process other events, but recursively invoking the event loop is always a bad idea because it can lead to difficult-to-debug side effects.

The best approach in this case (without resorting to multi-threaded Tcl scripts) is to break the operation into smaller chunks, and use after to schedule each of the chunks in sequence. Here's an example:

Code:
# Execute several iterations of a calculation.
# We get the starting value, the number of
# iterations to run, the ending value as
# arguments, and the number of milliseconds
# to pause between chunks as arguments.

proc UpdateTimer {start iterations stop pause} {
    global x timer

    # Stop either at the end or after the
    # given number of iterations, whichever
    # comes first.

    if {$start + $iterations > $stop} {
        set bound $stop
    } else {
        set bound [expr {$start + $iterations}]
    }

    # Here's our long-running calculation.
    # We're just summing a lot of random numbers.

    for {set i $start} {$i <= $bound} {incr i} {
        set x [expr {$x + rand()}]
    }

    # If we have more iterations to run, schedule
    # the next call to ourselves, starting where
    # we left off.

    if {$bound < $stop} {
        incr bound
        set timer [after $pause [list             UpdateTimer $bound $iterations $stop $pause]]
    } else {
        set x &quot;$x (done)&quot;
        .start configure -state normal
        .stop configure -state disabled
    }
}

proc StopTimer {} {
    global x timer
    set x &quot;Stopped&quot;
    if {[info exists timer]} {
        after cancel $timer
        unset timer
    }
}

set x &quot;Stopped&quot;
label .value -width 20 -anchor w     -textvariable x

button .start -text &quot;Start&quot; -command {
    .stop configure -state normal
    .start configure -state disabled
    set x 0

    # Add 100,000 random numbers, doing it
    # in chunks of 100 at a time, pausing 10
    # milliseconds between chunks. We can change
    # the chunk size and the pause to tune the
    # responsiveness of our application. 

    UpdateTimer 1 100 100000 10
}

button .stop -text &quot;Stop&quot; -state disabled -command {
    .start configure -state normal
    .stop configure -state disabled
    StopTimer
}

pack .value -padx 4 -pady 4
pack .start .stop -side left -padx 4 -pady 4
- Ken Jones, President, ken@avia-training.com
Avia Training and Consulting, 866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks Avia! It was just the thing I wanted to come to!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top