vwait

Difference between version 60 and 61 - Previous - Next
'''`[http://www.tcl.tk/man/tcl/TclCmd/vwait.htm%|%vwait]`''', a [Tcl Commands%|%built-in] Tcl command, enters the [Tcl event loop%|%event loop] and only returns once
the indicated variable is modified.



** Synopsis **

    :   '''vwait''' ''varName''



** Documentation **

   [http://www.tcl.tk/man/tcl/TclCmd/vwait.htm%|%official man page]:   



** See Also **

   [event-oriented programming]:   

   [Tcl event loop]:   

   `[update]`:   

   [Update considered harmful]:   



** Description **

'''`vwait`''' enters the Tcl [event loop], and as events are processed, monitors the variable named by ''varName'' for changes, [return%|%returning] only once some event handler modifies ''varName''.  Thus, `vwait` effectively blocks execution of the foreground script until a modification of ''varName'' occurs.
As soon as the event handler that modifies ''varName'' returns, `vwait` returns. ''VarName'' is resolved relative to the [global] scope, so the following pattern is often useful:

======
vwait [namespace which -variable foo]
======

Since the event handler that stores a value to ''varName'' continues to completion before `vwait` can return, some time can pass betweeen when a value is stored to ''varName'' and when `vwait` returns. 
For example, if an event handler sets
''varName'' and then itself calls '''vwait''' to wait for a different variable,
then the outer `vwait` depends on the inner `vwait`, and may not return for a long time.  In general, nested calls to `vwait` should be avoided. 



** `vwait forever` **

''Wish'' has a built-in event loop. Tclsh has one too but enters that only on demand, for which the [idiom] is to write at the end of code

======
vwait forever
======

[RS]:  ''forever'' being the name of a variable that is presumably never used, but you can set ''forever'' to any value to terminate such a Tcl script).

See the note below about namespaces - missing this note results in problems that occur quite commonly!



** Timeout for `vwait` **

Wai Shun Au wrote in [comp.lang.tcl]:

======
after 30000 {set a $a}
vwait a
======

This way it would wait for ''a'' to be changed or until 30 seconds is up.

[Jeffrey Hobbs] commented:  You found the standard way, but you have to go a
bit further to avoid weird bugs.  Cache the [after] id and make sure to cancel
it following the ''vwait'' (no [catch] needed - if the after id no longer
exists, because it was triggered, ''after cancel'' doesn't care).  That way you
won't get ''a'' being reset no matter what in 30 secs.

----

[DKF]:  You can '''`vwait` on several variables simultaneously''' as long as
all those variables are in the same array, and you are happy for any set of the
array to cause the `vwait` to terminate.  Do this by making the `vwait` be
on the overall array, and not any element of it.

----

[BBH]:  Actually, you can combine the multiple variable & timeout nicely
without the variables having to be related, and the timer won't affect the
actual variables, Demonstrated by this code taken from dissussion on
[comp.lang.tcl] (most of the original work by [Donald Porter], that I
tweaked a little to add timeout option).

kenstir:  I have further tweaked this version of waitForAny that returns the
var (or vars) that got set during the vwait.  This allows you to build a robust
asynchronous queue.  I'm submitting it as a patch to tcllib.sourceforge.net
along with tests.

======
namespace eval control {
    namespace export waitForAny
    variable waitForAnyKey 0

    # new "vwait" that takes multiple variables and/or optional timeout
    # usage:  waitForAny ?timeout? variable ?variable ...?
    proc waitForAny {args} {
        variable waitForAnyArray
        variable waitForAnyKey

        # if first arg is a number, then that is max wait time
        if {[string is int [lindex $args 0]]} {
            set timeout [lindex $args 0]
            set args [lrange $args 1 end]
        }

        # create trigger script that will cause vwait to fall thru
        # (trailing comment is to eat appended args in trace command)
        set index "Key[incr waitForAnyKey]"
        set trigger "[namespace code [list set waitForAnyArray($index) 1]] ;#"

        # create trace to trip trigger
        foreach var $args {
            uplevel \#0 [list trace variable $var w $trigger]
        }

        # set timer is user requested one
        if {[info exists timeout]} {
            set timerId [after $timeout $trigger]
        }
        vwait [namespace which -variable waitForAnyArray]($index)

        # remove all traces
        foreach var $args {
            uplevel \#0 [list trace vdelete $var w $trigger]
        }

        # cancel timer
        if [info exists timerId] {
            after cancel $timerId
        }
        # cleanup
        unset waitForAnyArray($index)
    }
}
======



** `vwait` in namespaces **

The varName must be globally qualified as if in a
binding, even if the vwait is inside a `namespace eval`':

======
namespace eval foo {
    vwait bar        ;# will never fire
    vwait ::foo::bar ;# does the job

    variable bar     ;# These two lines also do the job
    vwait [namespace which -variable bar]    ;# DGP
} ;# RS
======

** Avoiding Nested Calls to `vwait` **

A majority of the coding questions received in [comp.lang.tcl]
about `vwait` appear to result from deep misunderstandings of the command (as
opposed to mere syntactic confusion, for example). [Bruce Hartweg] has rightly
advised that its proper use is restricted:  "IMHO vwaits shouldn't be used too
much (the nesting issue creates unexpected results) because you are trying to
force a synchronous approach.

Here a simple example of nested vwait (this code never reach puts) :
======
set foo {}set forever 1

after 100 {
   set ::foo bar   set ::forever 1
   vwait ::forever
}

vwait ::foo
puts "foo is $foo"
======

In an event world, it is much better to keep everything event driven.
Occasionally for simple things (like dialogs) to use a vwait to avoid having to
break something the has a couple of file picks and/or confirmations into
umpteen parts has its place."  [KBK] agrees that the [Tcl event loop] is widely
misunderstood and discusses related issues in [Update considered harmful] and
the pages to which it links.

----

Packages such as [Tk] and [tclsvc] themselves call `vwait`.  To avoid nested `vwait` calls:

======
if {![info exists tk_version] && ![info exists tcl_service]} { ...
======

[PS] 2004-03-09:  For example, this script, which implements a TCP server in
tclsh and wish, will not exit properly when you close its main window:

======
proc accept { channel peer port } {
    close $channel
}
socket -server accept 5000
vwait forever
======

After you close the window, the wish app does not exit, but remains fully active in memory.

[DGP]:  This discussion is essentially another
vote in favor of [http://sf.net/tracker/?func=detail&aid=456548&group_id=12997&atid=362997%|%Tk Feature Request 456548].

The flaw here is in Tk's continued assumptions about being in wish, rather than
being an independent package that might be loaded into any Tcl interp.  There's
nothing wrong with `vwait` (at least nothing revealed in this discussion. :)
).

----

[Chris Nelson] said [http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/3ebbe18159a8efac?tvc=2&q=multiple+vwaits%|%these golden words%|%] on [comp.lang.tcl]:

''Multiple vwaits nest, they do not happen in parallel.  The outermost vwait cannot complete until all others return.''

----



'''[Marty Backe] 2002-08-15:'''


This little program demonstrates what [Chris Nelson] stated above (calls to `vwait` can nest):

======
set ::time 0
set ::a 0
set ::b 0

proc a_vwait {} {
    puts "Waiting 15 sec for ::a"
    vwait ::a
    puts "::a set"
}

proc b_vwait {} {
    puts "Waiting 30 sec for ::b"
    vwait ::b
    puts "::b set"
}


proc timer {} {
    incr ::time 1
    puts "$::time sec"
    if {$::time == 35} {
        exit
    } else {
        after 1000 timer
    }
}

after 1 a_vwait
after 5 b_vwait
after 10 timer

after 15000 {set ::a 0}
after 30000 {set ::b 0}

vwait forever
======

Although there are two events set to trigger in 15 sec and 30 sec respectively, the 30 sec vwait blocks the 15 sec vwait, opposite of the intuitive reaction to this program.

When you understand this code snippit, you'll be free from the dangers of haphazardly using vwait.

----

Christian Klugesherz 2009-12-04:  To understand what really happens, just
replace the above timer procedure, by adding [after info] which shows the existing
event handler queue 

======
proc timer {} {
    incr ::time 1
    puts "$::time sec"
    puts [after info]
    if {$::time == 35} {
        exit
    } else {
        after 1000 timer
        #puts [after info]
    }
}
======

----

Peter Newman 2004-03-09: '''Wish Is Buggy!''' It seems to me that all these
problems with `vwait`, `[tkwait]`, `[update]` and ''people not understanding the
event loop'', etc - are due to the inherently defective (in my opinion buggy)
design of wish - in that it automatically appends the event loop onto the
scripts it runs.

Take the following "Hello World" program:-

======
pack [button .mybutton -text {Hello World!} -command exit] ;
======

It's buggy! I forget to call the event loop. So it does nothing. But wish will
run it Ok. So wish's bug cancels out my bug - and the bug in my program gets
overlooked. Almost all programs on this Wiki are like that. As written by the
programmer, there's no call to the event loop.

Now with "Hello World" programs it probably doesn't matter. But with
complicated real world programs the event loop matters a lot. But wish permits
and encourages Tcl programmers to ignore the event loop. We only worry about it
when the roof caves in! Then we find out we haven't got a clue how it works. Or
how to write code that handles the event loop properly. (Then it's thank God
for pages like this on the Wiki, as you try and figure it out.)

The solution is to get rid of the auto-appending the event loop from wish. And
force Tcl programmers to learn and think about the event loop from the first,
and with every subsequent, script they write.

----

[schlenk]:  Not really, wish is ok, but there are tendencies to get rid of it
in favor of tclsh and package require Tk. Tk starts the event loop by default,
so there isn't any bug. If you use Tk, you have a running event loop. Why make
things more complex by forcing people to require the event loop explicitly if
it is clear they need it?

----

[PS] 2004-03-09:  I'd have to agree with schlenk that the current wish
behaviour is the right thing, but the whole reason I moved the 'check for tk'
remark to the top of the page is that I was bitten by it *again*. Currently, if
you want the 'default' Tcl event loop, the recommended/standard way seems to be
to call `[vwait forever]`. But there are more and more script out there that
run in multiple environments, with different capabilities, and more will
emerge. Is checking for tk and tclsvr enough right now? Probably. Will it be in
the future? Probably not. What we really need is a function that I can call to
start the event loop, which -by default- will never return. Something like:

======
interp eventloop

# or just
eventloop

# and for the special case which needs it:

{ ... appInitCode }
eventloop -pleaseReturnIfPossible
{ ... appCleanupCode }
======

Or bastardise `vwait` to do the same, when called with forever as its first
argument (which would probably break things). And maybe [[interp eventloop
{script}]] to provide the appropriate eventloop handler code.

----

[NEM]:  It is worth pointing out here that [tk_messageBox] calls vwait
internally (at least on UNIX). This has been a cause of several bugs in event
driven code I have written. All the advice given on this page about not calling
vwait should also apply to tk_messageBox. In particular, don't do something
like the following:

======
fileevent $fd readable [list readdata $fd]
proc readdata fd {
    global line
    set line [gets $fd]
    # Some other stuff...
    tk_messageBox ...
    # Some more stuff...
}
======

The problem is that the nested `vwait` means that `readdata` will
likely be re-entered, causing all kinds of mayhem to ensue. The way I coded
around this was to implement my own dialog box code, which took a callback:

======
messagebox ... -command [list dosomething ...]
======

But then, you still have to deal with what to do with events that come in while
the dialog is still raised. (Do you process them, or should you wait until the
dialog box is dismissed?) I never really decided what the ''correct'' behaviour
in this situation would be.

----

[CMcC]:  In case it matters to you, this quickie will prevent recursive `vwait` while permitting iterative `vwait`:

======
rename ::vwait ::vwait_org
proc ::vwait varname {
    rename ::vwait ::no_vwait
    set result [uplevel 1 [list vwait_org $varname]]
    rename ::no_vwait ::vwait
    return $result
}
======


** Subtleties with `vwait` and `[unset]` **

Typical usage examples of `vwait` involve waiting until an unset variable is `[set]` or an existing variable is updated.  The wording used in the `vwait` manual page is consistent with this pattern: it runs the event loop "until a variable is written", and also "It continues processing events until some event handler sets the value of the global variable".  Apparently, however, [unset%|%unsetting] the variable will also cause `vwait` to return!  For example:

======
set ready 1
after 3000 {unset ready}
vwait ready
# Returns after 3 s.
======

I found this a bit surprising, and counter-intuitive.  If using vwait to implement blocking of a shared resource, for example, this means that you can't just `vwait`, you have to do something like:

======
while {![info exists ready]} {vwait ready}
======

or

======
while {!$ready} {vwait ready}
======

(To me, having to use a loop like this seems to go against Tcl's event-driven style.)

Intriguingly, even if the unset fails (if the variable does not exist), `vwait` will still return:

======
after 3000 {catch {unset nonexistent}}
vwait nonexistent
# Returns after 3 s!
======

----

Ken: I have one a problem with the follow code. I intended to transfer control
to other parts of my program so i intend embed to use this code into my program
to halt the procession of one function and go to other function which would
call it again? Below is the code

======
set Flag 0
if {$Flag} {
    continue
} else {
    vwait Flag
}
======

but for this code it seems that it would prompt an error code which is "can't
wait for variable Flag" if flag stays at 0. So how should i proceed so that it
would not prompt this error code.





** `after` Pauses the [Event Loop] **


'''[Aj] 2015-02-03 00:51:35'''

I've read all the above and have been bitten by incorrectly assuming an "after 2000" command will wait while other events continue to be processed. My Tk app has many "wait" windows or pauses in a functions that allow time for other backgrounds commands to do their job. The problem is that using "after 5000" within a function disables all the buttons in the application. 

So, I use the following simple "pause" function in place of "after":

======
proc pause {ms {waitvar WAITVAR}} {
    global $waitvar
    after $ms "set $waitvar 1"
    puts "waiting $ms for $waitvar"
    vwait $waitvar
    puts "pause $ms returned"
}
button .b -text PressMe -command {pause 5000 but[incr i]}; # everyone waits on this
pack .b
after 0 {pause 1000 var1}; pause 3000 var2; # works as expected
after 0 {pause 3000 var3}; pause 1000 var4; # both return after 3 secs
======

Now that I understand that calls to `vwait` might be nested (thanks to this wiki!), the behavior is expected. My button is always responsive, but if pressed, all other vwaits are held up for at least another 5 seconds. And a second press within 5 seconds also delays the first one.

I don't hate this. It does seem almost too simple a solution, so I'd like to get comments as to what problems I might not have though of.

[aspect]:  If you want `[after]`-like behaviour in a procedure without blocking or `vwait`, one nice way to do it is with [coroutine%|%coroutines].  [tcllib%|%tcllib's] [https://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/coroutine/tcllib_coroutine.html%|%coroutine%|%] and [https://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/coroutine/coro_auto.html%|%coroutine::auto] packages make it all very convenient.  A simple example (without using those convenience modules) might look like this:

======
proc do_stuff {w} {
    set orig [$w cget -text]
    $w configure -state disabled
    for {set i 0} {$i < 5} {incr i} {
        $w configure -text "Thinking ($i)"
        after 1000 [info coroutine]
        yield
    }
    $w configure -text $orig
    $w configure -state normal
}

pack [button .b -text "Press Me" -command {coroutine do_stuff_coroutine do_stuff .b}]
======

<<categories>> Tcl syntax | Arts and Crafts of Tcl-Tk Programming | Command