You can't generally cancel a running coroutine easily. You can of course do ====== rename runningCoro "" ====== But then: * the coroutine doesn't know it was cancelled(*), no error is raised inside it, it just ceases to exist * if the coroutine has scheduled itself as a callback like in e.g. ====== ::chan event $chan readable [list [info coroutine]] ====== just like say [coroutine::util gets] does, then that event will produce an error. Unfortunately I found no way to handle coroutine cancellation without resorting to callbacks. The goods news is tcl allows you to [trace] command deletion like: ====== proc cleanup {args} {...} proc coro {} { ... trace add command [info coroutine] delete [list cleanup $arg1 $arg2] ... } ====== Which will allow it to e.g. unsubscribe from fileevent. Quite awkward: another proc has to be created and it also has to accept mostly useless args that [trace] passes to it in addition to any useful args we may pass from inside the coro. A better solution is trace not just command deletion, but leaving the current command's scope, i.e. [Deferred evaluation], especially since the same cleanup often is done upon normal exit. To sum up, here's one particular solution I could come up with for cancellable io with coroutines: ====== namespace eval cio {} proc cio::defer {args} { if {[llength $args]==1} {set args [lindex $args 0]} ;# flatten if 1 elem uplevel {set _defer_var {}} uplevel [list trace add variable _defer_var unset [list apply [list args $args]]] } proc cio::gets {{chan stdin} args} { ::chan configure $chan -blocking 0 defer fileevent $chan readable {} while {[set res [uplevel gets $chan {*}$args]] eq ""} { fileevent $chan readable [info coroutine] yield } set res } proc cio::puts {args} { switch -- [llength $args] { 1 {set chan stdin} 2 {set chan [lindex $args 0]} 3 {set chan [lindex $args 1]} default {tailcall ::chan puts {*}$args; #calling original with bogus args } } ::chan configure $chan -blocking 0 defer fileevent $chan writable {} fileevent $chan readable [info coroutine] yield uplevel gets $chan {*}$args } proc cio::after {} { set id [::after $delay [list [info coroutine]]] defer after cancel $id yield } proc ::cio::read {args} { set chop no ; # !-nonewline set total "" ;# "" = until eof # parse args if {[set chan [lindex $args 0]] eq "-nonewline"} { set chop yes set chan [lindex $args 1] } else { set total [lindex $args 1] } ::chan configure $chan -blocking 0 # run loop set buf {} ::chan event $chan readable [info coroutine] defer ::chan even $chan readable {} if {$total eq ""} {; # Loop until eof while {![::chan eof $chan]} { yield append buf [::chan read $chan] } } else { # Loop til eof or $total chars read while {$total >0 && ![::chan eof $chan]} { set chunk [::chan read $chan $total] append buf $chunk incr total -[string length $chunk] } } if {$chop && [string index $buf end] eq "\n"} { set buf [string range $buf 0 end-1] } return $buf } ====== None of these will not cause background errors should its caller be destroyed during the call unlike coroutine::util. They may of course cause numerous other errors as this is hardly tested! It's still sub-optimal: no error is raised in the caller, and if the callers wants to do some of its own cleanup, it's going to have to defer too. Less straightforward than something like ====== proc coro {} { if {[catch {imaginary_cancellable_gets $chan} err]} { if {$err eq "cancelled"} { } } } ====== would be. But that's not possible to do with the current tcl coro mechanism, is it?