Reputation: 4408
I am still new to TCL, don't use it much, mostly in legacy code. I'm having trouble with a code, and by simplifying it I got the code below. In the second "after" I have a code that sometimes takes longer, sometimes goes faster than the first "after". I want my code to proceed whenever either of them finishes, ignoring the other. However, my vwait is always waiting for the latest "after" I have.
global tkf
set tkf b
puts "ini: $tkf"
# wait 7 seconds, then set tkf
set aid7 [after 7000 set tkf Y7]
puts "7: $tkf ($aid7)"
# wait 4 seconds, then wait 15 seconds, then set tkf
after 4000 {
puts "will wait 15s"
after 15000
puts "waited, will set tkf 4"
set tkf Y4
}
puts "4: $tkf"
vwait tkf
puts "vwait $tkf"
puts "end"
I would expect this to trigger my vwait at 7 seconds, and proceed. But this is the output I get:
E:\>tclsh vwait_test.tcl
ini: b
7: b (after#0)
4: b
will wait 15s
waited, will set tkf 4
vwait Y4
end
Which means it waited the 15 seconds and triggered the second code that sets tkf. I thought it would output:
will wait 15s
vwait Y7
end
I have no idea why this is its behaviour or how to change it, and I couldn't get any hints at the documentation pages. I'd appreciate if someone can explain this behaviour to me, and how to get what I want. Then I can adapt it to my own code.
Upvotes: 1
Views: 3614
Reputation: 385900
It's been a while since I've delved this deeply into tcl, but it looks like after 4 seconds you are putting the whole app to sleep for 15 seconds. When you do after 15000
, nothing can happen for 15 seconds, because tcl is single threaded.
To be a bit more clear: there's a difference between after X
and after X {script}
. The former is the same as a "sleep" command -- the program literally pauses for that amount of time. The latter, rather than sleeping, schedules some code to run at some point in the future.
This is documented on the after man page. Here's a relevant quote:
after ms Ms must be an integer giving a time in milliseconds. The command sleeps for ms milliseconds and then returns. While the command is sleeping the application does not respond to events.
The above was taken from http://tcl.tk/man/tcl8.5/TclCmd/after.htm#M5
In this context, "events" includes any other scripts scheduled with after
This paragraph immediately follows that in the man page:
after ms ?script script script ...? In this form the command returns immediately, but it arranges for a Tcl command to be executed ms milliseconds later as an event handler. The command will be executed exactly once, at the given time. The delayed command is formed by concatenating all the script arguments in the same fashion as the concat command. The command will be executed at global level (outside the context of any Tcl procedure). If an error occurs while executing the delayed command then the background error will be reported by the command registered with interp bgerror. The after command returns an identifier that can be used to cancel the delayed command using after cancel.
Upvotes: 2
Reputation: 4408
For the sake of documentation and future reference, I'm posting an expanded explanation of what is happening from what I understood based on comments by schlenk and Bryan Oakley. Thank you both.
The after
with a command is not sleeping in the background to be triggered at that time. Instead, it is saved in a structure called "event loop", and will only be executed when some specific commands are called, commands that cause the event loop to be processed.
Here is what is happening in the code:
First, it adds after 7000
code (set tkf Y7
) to the event loop, to be executed 7 seconds later. This will only be executed if/when the event loop is triggered, when it will check the 7 seconds condition.
Then it adds the after 4000
block to the event loop, also without executing it.
Then it reaches the vwait
. This command will trigger the event loop, but there is nothing to be executed now, so it does nothing. Because vwait
will keep running the event loop until the variable changes, the main code will not move forward. Instead, vwait
will remain repeating the event loop until something happens. But nothing happens in the first 4 seconds. Finally, after 4 seconds, the event loop notices that the after 4000
block is ready to execute. So, the event loop executes that block in its entirety.
The after 4000
block will print a message and sleep for 15 seconds. At this point, the main code is not running because it's waiting for vwait
. And vwait
was running the event loop, and is now waiting because the event loop found an event to execute; the event loop is running this block, which is executing a sleep. Once 15 seconds have passed, we proceed still within the block, printing another message and setting tkf with set tkf Y4
. After that, the block is finished.
The event loop still have one pending event that it could run, the after 7000
, whose condition was not met the last time the event loop checked it but would be met if the event loop checked it now. However, when the block finished running, the vwait
condition was met. That means the event loop will not move on to the next event, and therefore it will not execute the after 7000
, and the control will return to the main code with its puts "vwait $tkf"
.
Things are never run in parallel, and there's always only one thread. The "event loop" is a parallel structure, but it doesn't run independently. This means it's impossible to make what I want using after+vwait. I wanted to proceed in my main code after whichever finished running first, under the impression that they were running in parallel. But the first to finish will always be the first to start running, because they don't run in parallel. Also, my interpretation that vwait was always waiting for the latest "after" was wrong. Actually, it's just waiting for the shortest "after".
Now on to find an alternative, probably with threads... :-/
Upvotes: 3