2
votes

I am a beginnner in Tcl. I am trying to learn Tcl without getting involved in Tk. I came across commands like vwait and after but I was confused as most explanations involved the notion of event loop and further mostly demonstrated the concept by using Tk. I would like to understand the notion of events and event loop and how the commands I mentiond relate to them, please refere me some reference for this. The explantion should not use Tk as examples, use no Tcl extensions, assume no prior knowledge of events. Some (minimal) toy examples and real-wordl application of tcl event loop except GUI programming/Tk would be appreciated.

I came across this tutorial on Tclers wiki. I am looking for OTHER reference or explanation like this.

1

1 Answers

4
votes

If you're not using Tk, the main reasons for using the event loop are for waiting in the background while you do some other I/O-bound task, and for handling server sockets.

Let's look at server sockets.

When you open a server socket with:

socket -server myCallbackProcedure 12345

You're arranging for an event handler to be set on the server socket so that when there is an incoming connection, that connection is converted you a normal socket and your supplied callback procedure (myCallbackProcedure) is called to handle the interaction with the socket. That's often in turn done by setting a fileevent handler so that incoming data is processed when it arrives instead of blocking the process waiting for it, but it doesn't have to be.

The event loop? That's a piece of code that calls into the OS (via select(), poll(), WaitForMultipleObject(), etc., depending on OS and build options) to wait until something happens on any nominated channel or a timeout occurs. It's very efficient, since the thread making the call can be suspended while waiting. If something happens, the OS call returns and Tcl arranges for the relevant callbacks to be called. (There's a queue internally.) It's a loop because once the events are processed, it's normal to go back and wait for some more. (That's what Tk does until there are no more windows for it to control, and what vwait does until the variable it is waiting for is set by some event handler.)

Asynchronous waits are managed using a time-ordered queue, and translate into setting the timeout on the call into the OS.

An example:

socket -server myCallbackProcedure 12345
proc myCallbackProcedure {channel clientHost clientPort} {
    puts "Connection from $clientHost"
    puts $channel "Hi there!"
    flush $channel
    close $channel
}
vwait forever
# The “forever” is an idiom; it's just a variable that isn't used elsewhere
# and so is never set, and it indicates that we're going to run the process
# until we kill it manually.

A somewhat more complicated example with asynchronous connection handling so we can serve multiple connections at once (CPU needed: minimal):

socket -server myCallbackProcedure 12345
proc myCallbackProcedure {channel clientHost clientPort} {
    puts "Connection from $clientHost"
    fileevent $channel readable [list incoming $channel $clientHost]
    fconfigure $channel -blocking 0 -buffering line
    puts $channel "Hi there!"
}
proc incoming {channel host timeout} {
    if {[gets $channel line] >= 0} {
        puts $channel "You said '$line'"
    } elseif {[eof $channel]} {
        puts "$host has gone"
        close $channel
    }
}
vwait forever

An even more complicated example that will close connections 10 seconds (= 10000ms) after the last message on them:

socket -server myCallbackProcedure 12345
proc myCallbackProcedure {channel clientHost clientPort} {
    global timeouts
    puts "Connection from $clientHost"
    set timeouts($channel) [after 10000 [list timeout $channel $clientHost]]
    fileevent $channel readable [list incoming $channel $clientHost]
    fconfigure $channel -blocking 0 -buffering line
    puts $channel "Hi there!"
}
proc incoming {channel host timeout} {
    global timeouts
    after cancel $timeouts($channel)
    if {[gets $channel line] >= 0} {
        puts $channel "You said '$line'"
    } elseif {[eof $channel]} {
        puts "$host has gone"
        close $channel
        unset timeouts($channel)
        return
    }
    # Reset the timeout
    set timeouts($channel) [after 10000 [list timeout $channel $host]]
}
proc timeout {channel host} {
    global timeouts
    puts "Timeout for $host, closing anyway..."
    close $channel
    unset -nocomplain timeouts($channel)
}
vwait forever