问题
I'd like to find a nice way in Tcl to detect the end of a coroutine. Consider:
coroutine cor apply {{} {
yield 1
yield 2
yield 3
}}
try {
puts [cor]
puts [cor]
puts [cor]
puts [cor]
} trap {TCL LOOKUP COMMAND cor} {e} {
puts "done"
}
This works, but it feels like a hack and it is brittle. If I rename cor
and forget to rename it in the trap, it fails. If I leave out the cor
in the trap, it will catch unrelated typos.
There's got to be a better way. What is it?
回答1:
To detect if a command still exists, use info commands
and check the length of list it returns. (I'm assuming you can guarantee that there are no glob characters in the command's name; if that's true, the check is very cheap.)
while {[llength [info commands cor]]} {
puts [cor]
}
However, if you are intending the coroutine to work as a generator and only be used in a loop, you can get it to signal that it is done directly. To do that, make it produce a break
.
coroutine cor apply {{} {
yield [info coroutine]; # A sensible result from [coroutine]
yield 1
yield 2
yield 3
return -code break
# [tailcall break] would also work
}}
while true {
puts [cor]
}
The trick is that you don't yield
the break; you use it as the terminal result of the coroutine. (Your old coroutine was using the result of the yield
as that, probably principally by luck.)
In writing this, I've noticed a bug. That loop to run the coroutine doesn't work at the top level in 8.6.8 (the only version I've checked with). No idea why. It's fine in a procedure/apply
context (where it is natural to pass the coroutine name as an argument):
apply {c {
while true {
puts [$c]
}
}} cor
The bytecode for these various ways of running does not appear meaningfully different. Forcing interpretation also makes things work, which is a great indication that this is a real bug.
set while while
$while true {
puts [cor]
}
来源:https://stackoverflow.com/questions/54273578/how-to-detect-the-completion-of-a-tcl-coroutine