sleep

Why does Tcl not come with a sleep command? Why, it just doesn't call it sleep. It calls it after.


The TclX package includes a sleep command (with syntax similar to the POSIX shell command).

sleep seconds

Puts the process (or at least the current thread) to sleep for seconds seconds (truncated to an integer). Note that the OS may wait for longer than requested before waking the process up.

The Expect package's sleep is equivalent.

Note that Tcl's built-in after command uses delay units of milliseconds whereas the TclX/Expect command works with seconds (i.e., a factor of 1000 different). Be careful when converting.


An Event-Handling Sleep

However, Jeff Hobbs notes in a discussion held elsewhere:

[someone proposed the code ]

  proc sleep {time} {
      after $time set end 1
      vwait end
  }

[and Jeff responds:]

This [the above code] is actually not correct because vwait must receive a globally scoped variable, so add global end above (or rather use a less common global name).

This has advantages over regular after $time because it does allow other events to continue processing, but that difference should be understood.

Also, when using the form of after that takes a command in Tcl without Tk present, you have to make sure that vwait is called (or update), to kick in the event loop.


This was a horribly misguided thing... dangerously appealing and guaranteed to create snake balls.

I tried to remove the code below from this page, but some evil genius put it back!


Will suspend evaluation of current code block, but allow the event loop to continue handling pending events.

This particular version of uniqkey is my favorite because the keys generated will generally sort in time created order.

 proc uniqkey { } {
     set key   [ expr { pow(2,31) + [ clock clicks ] } ]
     set key   [ string range $key end-8 end-3 ]
     set key   [ clock seconds ]$key
     return $key
 }

 proc sleep { ms } {
     set uniq [ uniqkey ]
     set ::__sleep__tmp__$uniq 0
     after $ms set ::__sleep__tmp__$uniq 1
     vwait ::__sleep__tmp__$uniq
     unset ::__sleep__tmp__$uniq
 }

Example:

 after 4000 puts foo!
 sleep 5000
 puts bar!

-Phil Ehrens

Using Coroutines

DKF: Provided you have an event loop already going and you're in a Tcl 8.6 coroutine, you can do an event-handling sleep without the reentrancy troubles:

proc sleep {ms} {
    after $ms [list [info coroutine]]
    yield
}

GPS: You swilly wabbits. :)

 $ tclsh8.4
 % interp alias {} sleep {} after
 sleep
 % sleep 400
 % interp alias {} wait {} sleep
 wait
 % wait 800

Fine-Grained Sleeping

after has problems with fine-grained delays, at least on my system; its probably related to unix timeslicing.

    % time {after 1} 100
    10006 microseconds per iteration

So asking for a 1ms delay gets you something closer to 10ms. I'm in need of something much closer to accurate, so I came up with two variants of a busywaiting delay. They watch clock clicks to see when the appropriate amount of time has passed. The first, delay-bw is a pure busy-wait, while delay-ev uses the event loop.

    namespace eval delay {
        variable _i
        variable c/ms

        proc calibrate {} {
            variable c/ms
            puts "calibrating clock clicks.."
            set start [clock clicks]
            after 1000
            set end [clock clicks]
            set c/ms [expr {($end-$start)/1000.0}]
            puts "speed: [expr {${c/ms}*1000}] clicks per second"
        }

        calibrate

        # busywaiting delay
        proc delay-bw {sec} {
          variable c/ms
          set s [clock clicks]
          while {[clock clicks] < $s+(${c/ms}*$sec)} {
            # do nothing
        }

        # busywaiting "after idle" delay, using event loop
        proc delay-ev {sec} {
          variable c/ms
          set s [clock clicks]
          set e [expr {$s+$sec*${c/ms}}]
          evwait ::delay::_i $e
          vwait ::delay::_i
          unset ::delay::_i
        }

        # worker for delay-ev
        # continually reschedules itself via "after idle" until end time
        proc evwait {var {end 0}} {
            set ct [clock clicks]
            if {$ct < $end} {
                after idle [list ::delay::evwait $var $end]
                return
            } else {
                set $var 0
            }
        }
    }

I get much more accurate delays using these:

    % time {::delay::delay-ev 1} 1000
    1283 microseconds per iteration
    % time {::delay::delay-bw 1} 1000
    1225 microseconds per iteration

JR