Synopsis edit
- after ms
- after ms script ?script script ...?
- after cancel id
- after cancel script script script ...
- after idle ?script script script ...?
- after info ?id?
See Also edit
- Tcl event loop
- Includes information on how to enter the event loop
- bgerror
- update
- vwait
- every
- after command extended
- bgsleep
- idle
- an example of how to cache idle commands
- Tail call optimization
- AM using after to create tail-recursive procedures
- An analog clock in Tk
- Powered by after. KBK 2000-11-15: Countdown program has a better discussion of what's going on. (I feel justified in saying this, since I wrote both of them.)
- Lessons Learned: Doing idle/periodic processing with "after", comp.lang.tcl, 1998-02-13
Description edit
Any script arguments are concatenated together, delimited by whitespace, to form a single script, and are scheduled to be evaulated at least 'ms'' milliseconds in the future. The exact duration of the delay is operating system-dependent. Evaluation of the script occurs at level #0. after returns an identifier for the scheduled event. This is the asynchronous mode of after.If script is not provided, after sleeps synchronously for at least the specified number of milliseconds and then returns.If the first argument is one of the subcommands, cancel, idle or info, the corresponding subcommand is execcuted.In order for events scheduled using after to take place, the event loop must be servicing events.after uses the system time to determine when it is time to perform a scheduled event. This means that with the exception of after 0 and after idle, it can be subverted by changes in the system time.Asynchronous Mode Example edit
proc sync {} { after 1000 puts {message 1} puts {message 2} } proc async {} { after 1000 [list puts {message 1}] puts {message 2} }
Synchronous Mode (Sleep) Example edit
Here is a proc to make Tcl do nothing at all, i.e. sleep, for N seconds:proc sleep N { after [expr {int($N * 1000)}] }This arranges for the command wake_up to be run in eight hours (providing the event loop is active at that time):
after [expr {1000 * 60 * 60 * 8}] wake_up
Repeated Action edit
Repeated action is a typical application, e.g. this little timer from the Bag of Tk algorithms:See also, everyClock display on label:proc clock:set var { global $var set $var [clock format [clock seconds] -format %H:%M:%S] after 800 [list clock:set $var] } pack [label .l -textvariable myclock] clock:set myclock ;# call once, keeps ticking ;-) RSThis is not a recursion; the next execution of clock:set is started started long after the current has returned, and calling level is #0.Each time it executes, clock:set reschedules itself to be exeucted again 800 msec later.
At edit
Here's some sugar for after where you specify absolute time, like for a scheduler:proc at {time args} { if {[llength $args] == 1} {set args [lindex $args 0]} set dt [expr {([clock scan $time]-[clock seconds])*1000}] after $dt $args } ;# RS at 9:31 puts Hello at 9:32 {puts {Hello again!}}If you need something to schedule, this little alert packages details from tk_dialog away, and may reappear after 5 minutes:
proc alert {time text} { if [tk_dialog .[clock clicks] "Alert at $time" $text info 0 OK Re-Alert] { after 300000 [list alert $time $text] } } at 9:55 alert 10:00 "Meeting in 5 minutes"
after 0 ... edit
This schedules a script for immediate execution. It's useful for getting the tightest possible event. Warning: This places the scheduled event at the front of the queue, so a command that perpeually reschedules itself in this manner can lock up the queue.after x after idle ... edit
To schedule a script to repeat as fast as possible, one might be inclined to use after 0 like this:set script { #do stuff if {[not done]} { after 0 $script } }There's a problem, though. Presumably one is rescheduling the script in order to give the event loop a chance to service other events, but after 0 results in the script being placed at the front of the event queue, meaning that it will be the very next event to get serviced. As long as the script keeps scheduling itself at the head of the queue, the event loop will not service any other events. Probably not what was intended, unless you just have some irrational fear of while that compels you to avoid it at all costs.In order to give the event loop a little breathing room, one solution is to use after 1, instead of after 0, but wasting all those precious nanoseconds might make you itchy. Instead, do this:
after idle {after 0 {callSomeProc}}The following accomplishes the same purpose,
after 0 {after idle {callSomeProc}}Both of these accomplish the same goal: draining the standard queue, which allows update (and update idletasks) to move over to the other queue, but there's an advantage to calling after idle first: The returned identifier will be useful for while the script is sitting in the idle queue. if after 0 is called first, the returned event identifier is not the identifier for the script while it sits in the idle queue, so it can't serve to cancel that script.Here is an example in which the event loop gets stuck servicing the idle queue, starving the normal queue:
#warning: this causes the event loop to sping on the idle queue set foo 0 set script1 { set foo 1 } set script2 { if {$foo} { set done 1 } else { puts {not done} after idle $script2 } } after 5000 $script1 after idle $script2 update idletasks vwait doneThe idiom alleviates the problem. The events are channeled between queues, giving the update idletasks a chance to drain the idle queue.
set foo 0 set script1 { set foo 1 } set script2 { if {$foo} { set done 1 } else { puts {not done} after idle [list after 0 $script2] #this would accomplish the purpose just as well #after 0 [list after idle $script2] } } after 5000 $script1 after idle $script2 update idletasks vwait doneOne might consider solving the issue by simply not employing in a program both update idletasks and scripts that rescheduled themselves into the same queue. This would work where all the source code was under one's own management, but it just so happens that Tk calls update idletasks in various places, so if you're using Tk or any other library that might possibly call update or update idletasks, the best way to stay out of trouble is to always use this idiom when rescheduling an event.---Philip Smolen 2014-07-28: Is this what you mean by "after idle" body can't "after idle". https://groups.google.com/forum/#!topic/comp.lang.tcl/YqmL-MBjfLQ I'm looking at bad_idle_proc1.PYK 2015-03-12: Thanks for the link. It inspired me finally think through this whole thing, ask around, get some good feedback from kbk in the Tcl Chatroom, and rewrite the explanation above.In "Keep a GUI alive during a long calculation", kbk also explains the mechanism behind this pattern.
Invisible Errors edit
interp bgerror or its predecessor, bgerror, are scheduled for execution when an error occurs in a script queued by after. They essentially run with after idle priority, which means that they can be preempted by scripts scheduled via after that causes other tasks to run prior to interp bgerror :proc every {ms body} { after 1 [info level 0] if 1 $body } set ::j 0 after 0 { every 0 { puts {what is the length...} #normally the error notice the error in this line puts [string llength hello] incr ::j } } vwait ::jThe solution is to make sure scheduled interp bgerror actions get run:
proc every {ms body} { after 1 [after idle [list after 0 [info level 0]]] if 1 $body } set ::j 0 after 0 { every 0 { puts "what is the length..." #normally the error notice the error in this line puts [string llength hello] incr ::j } } vwait ::j
How to stop the execution of a procedure from within another procedure edit
Dependence on System Time edit
after depends on the system time, so changing the system time after something has been scheduled can cause unexpected behaviour.FW:proc again {} { puts Hello after 1000 again }Changing the system time backwards an hour in Windows as the script is running, ends the "Hello" output. I'm guessing the event loop schedules after ms scripts to occur at a certain fixed time, dependent on the system clock (so of course setting the time backwards will postpone scheduled "after" events), but WHY? Why not just use an internal clicker rather than the system clock? And more importantly (for my project) is there a way to avoid this behavior?notes from #Tcl irc channel, 2012-12-24 (paraphrased)kbk, Tcl Chatroom 2012-12-24, said that the reason for this is that, as he understands it, it's quite hard to do a monotonic clock in a portable manner. He also said ferrieux believes that after really should be an at command to schedule a task to wake up at a given wall clock time. Windows time-since bootload promised to be monotonic, but has only (typically) 20-50 ms precision, and overflows after a few weeks.Twylite 2013-11-15: The dependence on system time really should be noted in the man page. Moreover, the current Windows implementation trades off accuracy (against the wall clock) to gain precision (presumably for high-precision timing), and in doing so breaks both. When running under load, the calibration loop seems to lag and the clock (Tcl_GetTime) drifts by up to -1.1 seconds from the system time, then jumps to catch up (this is noted in KB274323). That jump can result in after (or any userland timing based on clock) returning up to 1.1s early or claiming an elapsed time of up to 1.1s too much, and also means that log timestamps are up to 1.1s out compared to those of other processes (whether Tcl or other language). I'll file this as a bug in the near future. Python PEP-418 is an excellent reference for implementing monotonic clocks.
Misc edit
Example: wait for program (executable) to become active
This example script waits until the configured program can be seen in in the Linux /proc filesystem.I use it in a startup script (yes, tclsh is fully functional) to wait for a service. Alternative solution: master the systemd dependencies to get it right.Techniques used: timed repeated execution, advanced glob search, how to time out, how to use /proc# execute this in intervals proc waitforproc {p interv} { set extrawait 500 foreach e [glob {/proc/[0-9]*/exe}] { if {[file readable $e]} { if {[file readlink $e] == $p} { after $extrawait set waitforme true return } } } after $interv waitforproc $p $interv } # called on timeout proc timeout {msg} { puts stderr $msg; exit} set progname /usr/bin/xv # start timeout after [expr {5 * 60 * 1000}] timeout {Time has run out, stopping.} # look for program waitforproc $progname 200 puts "\tstart waiting for $progname" # go into the event loop vwait waitforme puts "\tstopp waiting"
Dustbin edit
The information in this section has been deemed inaccurate, outdated, unhelpful, or misleading, and is scheduled for deletion unless someone moves it out of this section soon.RJM 2004-07-29: When short (< 10 ms), well defined intervals are desired, do not be tempted to use after nn. Instead useafter ''nn'' {set _ 0}; vwait _ ;# or another variable nameThis keeps the event loop alive. I found out that a simple after 1 may yield a very different result (Win98/266MHz 4-5 ms; W2K/1200MHz 15-16 ms), while the result is reasonable accurate when the code example above is used. But from after 2 on, both variations yield much too high delays (at least on the windows platform).
This script illustrates events at various intervals
proc print {} { global ary state puts "$state $ary($state)" } proc timer {} { global ary state num print after $ary($state) { set state [expr ($state+1)%$num] timer } } array set ary {0 100 1 200 2 300 3 400 4 500} set num [array size ary] set state 0 timer
caspian: When you use the "after" command to make your script wait for a period of time, the rest of your script will not wait up for the line(s) that are passed through to the after command. For example, this code:
puts "I know" after 500 {puts "Tcl"} puts "and Tk" vwait foreverWill output:
I know and Tk # Then, 500 milliseconds later: TclTo make "and Tk" appear after "Tcl", you must make "and Tk" wait for an equal or greater amount of time as "Tcl". To wit:
puts "I know" after 500 {puts "Tcl"} after 500 {puts "and Tk"} vwait foreverwhich will output:
I know # Then, 500 milliseconds later: Tcl and TkAnother way to solve this problem is by using vwait like this:
set wait 0 puts "I know" vwait wait after 500 {set wait 1} puts "and Tk"rdt says: don't you have to do the 'after 500 ...' _before_ you do the 'tkwait ...' ?? RJ - Absolutely - once in the event loop, no further commands are processed, so the after never gets registered. This is a wait forever.MG The other option is to just use the form of after which pauses execution completely, instead of the form caspian used which executes one particular command after a delay:
puts "I know" after 500 puts "Tcl" puts "and Tk"
AMucha 2008-07-28: after cancel script deletes exactly one instance! I accumulated heaps of after-procs in an overloaded text widget (trying to be super clever) with an
after cancel show:detail after idle {sfter 5000 show:detail}show:detail uses several functions of the text widget and (tried to) clear up with its own 'after cancel show:detail' at the end.Demo showing this:
proc hello {} {puts hello} for {set n 1} {$n<=4} {incr n} { after 20000 hello } foreach id [after info] { puts "$id [after info $id]" } after cancel hello puts "====================" foreach id [after info] { puts "$id [after info $id]" } exitDespite the word "match" in the manpage there is no globbing. eg 'after cancel hell*' does not work.