[Top] [Prev] [Next] [Bottom]

15

15 Event-Driven Programming

This chapter describes event-driven programming using timers and asynchronous I/O facilities. The after command causes Tcl commands to occur at a time in the future, and the fileevent command registers a command to occur in response to file input/output (I/O). Tcl commands: after, fblocked, fconfigure, fileevent, and vwait.
Event-driven programming is used in long-running programs like network servers and graphical user interfaces. This chapter introduces event-driven programming in Tcl. Tcl provides an easy model in which you register Tcl commands and the system calls those commands when a particular event occurs. The after command is used to execute Tcl commands at a later time, and the fileevent command is used to execute Tcl commands when the system is ready for I/O. The vwait command is used to wait for events. During the wait Tcl automatically calls Tcl commands that are associated with different events.

The event model is also used when programming user interfaces using Tk. Originally, event processing was only associated with Tk. The event loop moved from Tk to Tcl in the Tcl 7.5/Tk 4.1 release.

The Tcl Event Loop

An event loop is built into Tcl. Tcl checks for events and calls out to handlers that have been registered for different types of events. Some of the events are processed internally to Tcl. You can register Tcl commands to be called in response to events. There are also C APIs to event loop, which are described on page 556. Event processing is active all the time in Tk applications. If you do not use Tk, you can start the event loop with the vwait command as shown in Example 15-2 on page 180. The four event classes are handled in the following order:

The after Command

The after command sets up commands to happen in the future. In its simplest form it just pauses the application for a specified time, in milliseconds. The example below waits for half a second:

after 500
During this time the application processes no events. You can use the vwait command as shown on page 180 to keep the Tcl event loop active during the waiting period.

The after command can register a Tcl command to occur after a period of time, in milliseconds:

after milliseconds cmd arg arg...
The after command treats its arguments like eval; if you give it extra arguments it concatenates them to form a single command. If your argument structure is important, use list to build the command. The following example always works, no matter what the value of myvariable is:

after 500 [list puts $myvariable]
The return value of after is an identifier for the registered command. You can cancel this command with the after cancel operation. You specify either the identifier returned from after, or the command string. In the latter case the event that matches the command string exactly is canceled.

Table 15-1 summarizes the after command:
The after command.
after milliseconds Pause for milliseconds.
after ms arg ?arg...? Concatenate the args into a command and execute it after ms milliseconds. Immediately returns an ID.
after cancel id Cancel the command registered under id.
after cancel command Cancel the registered command.
after idle command Run command at the next idle moment.
after info ?id? Return a list of IDs for outstanding after events, or the command associated with id.

The fileevent Command

The fileevent command registers a procedure that is called when an I/O channel is ready for read or write events. For example, you can open a pipeline or network socket for reading, and then process the data from the pipeline or socket using a command registered with fileevent. Using network sockets is described in Chapter 15. The advantage of this approach is that your application can do other things, like update the user interface, while waiting for data from the pipeline or socket. You can use fileevent on stdin and stdout, too.

The command registered with fileevent uses the regular Tcl commands to read or write data on the I/O channel. For example, if the pipeline generates line-oriented output, you should use gets to read a line of input. If you try and read more data than is available, your application may block waiting for more input. For this reason you should read one line in your fileevent handler, assuming the data is line-oriented. If you know the pipeline will generate data in fixed-sized blocks, then you can use the read command to read one block.

The fconfigure command described on page 181 can put a channel into non-blocking mode. This is not strictly necessary when using fileevent. The pros and cons of non-blocking I/O are discussed later.

End of file makes a channel readable.

You should check for end of file in your read handler because it will be called when end of file occurs. It is important to close the channel inside the handler because closing the channel automatically unregisters the handler. If you forget to close the channel, your read event handler will be called repeatedly.

There can be at most one read handler and one write handler for an I/O channel. If you register a handler and one is already registered, then the old registration is removed. If you call fileevent without a command argument it returns the currently registered command, or null if there is none. If you register the empty string, it deletes the current file handler.

The following example shows a read event handler. Example 19-1 on page 234 also uses fileevent to read from a pipeline. A pipeline is opened for reading and its command executes in the background. The Reader command is invoked when data is available on the pipe. The end of file condition is checked, and then a single line of input is read and processed.

A read event file handler.
set pipe [open "|some command"]
fileevent $pipe readable [list Reader $pipe]
proc Reader { pipe } {
	if [eof $pipe] {
		catch {close $pipe}
		return
	}
	gets $pipe line
	# Process one line
}
Table 15-2 summarizes the fileevent command.
The fileevent command.
fileevent fileId readable ?command? Query or register command to be called when fileId is readable.
fileevent fileId writable ?command? Query or register command to be called when fileId is writable.

The vwait Command

The vwait command waits until a variable is modified. For example, you can set variable x at a future time, and then wait for that variable to be set with vwait.

set x 0
after 500 {set x 1}
vwait x
Waiting with vwait causes Tcl to enter the event loop. Tcl will process events until the variable x is modified. The vwait command completes when some Tcl code runs in response to an event and modifies the variable. In this case the event is a timer event, and the Tcl code is simply:

set x 1
In some cases vwait is only used to start the event loop. The following example sets up a file event handler for stdin that will read and execute commands. Once this is set up, vwait is used to enter the event loop and process commands until the input channel is closed. The process exits at that point, so the vwait variable forever is not used:

Using vwait to activate the event loop.
fileevent stdin readable StdinRead
proc StdinRead {} {
	global command prompt
	if [eof stdin] {
		exit
	}
	append command(line) [gets stdin]
	if [info complete $command(line)] {
		catch {uplevel #0 $command(line)} result
		puts $result
		puts -nonewline $prompt
		flush stdout
		set command(line) {}
	}
}
puts -nonewline $prompt
flush stdout
vwait forever

The fconfigure Command

The fconfigure command sets and queries several properties of I/O channels. The default settings are suitable for most cases. If you do event-driven I/O you probably want to set your channel into non-blocking mode. If you handle binary data, you can turn off end of line translations. You can query the channel parameters like this:

fconfigure stdin
-blocking 1 -buffering none -buffersize 4096 -eofchar {} -translation lf

Table 15-3 summarizes the properties controlled by fconfigure. They are discussed in more detail below.
I/O channel properties controlled by fconfigure.
-blocking Block until I/O channel is ready: 0 or 1.
-buffering Buffer mode: none, line, or full.
-buffersize Number of characters in the buffer.
-eofchar Special end of file character. Control-z (\x1a) for DOS. Null otherwise.
-translation End of line translation: auto, lf, cr, crlf, binary.
-mode Serial devices only. Format: baud,parity,data,stop
-peername Sockets only. IP address of remote host.
-peerport Sockets only. Port number of remote host.

Non-Blocking I/O

By default, I/O channels are blocking. A gets or read will wait until data is available before returning. A puts may also wait if the I/O channel is not ready to accept data. This behavior is OK if you are using disk files, which are essentially always ready. If you use pipelines or network sockets, however, the blocking behavior can hang up your application.

The fconfigure command can set a channel into non-blocking mode. A gets or read command may return immediately with no data. This occurs when there is no data available on a socket or pipeline. A puts to a non-blocking channel will accept all the data and buffer it internally. When the underlying device (i.e., a pipeline or socket) is ready, then Tcl automatically writes out the buffered data. Non-blocking channels are useful so your application can do something else while waiting for the I/O channel. You can also manage several non-blocking I/O channels at once. Non-blocking channels should be used with the fileevent command described earlier. The following command puts a channel into non-blocking mode:

fconfigure fileID -blocking 0
It is not strictly necessary to put a channel into non-blocking mode if you use fileevent. However, if the channel is in blocking mode, then it is still possible for the gets or read done by your fileevent procedure to block. For example, an I/O channel might have some data ready, but not a complete line. In this case a gets would block, unless the channel is non-blocking. Perhaps the best motivation for a non-blocking channel is the buffering behavior of a non-blocking puts. You can even close a channel that has buffered data, and Tcl will automatically write out the buffers as the channel becomes ready. For these reasons, it is common to use a non-blocking channel with fileevent. Example 15-3 shows a fileevent handler for a non-blocking channel. As described above, the gets may not find a complete line, in which case it doesn't read anything and returns -1.

A read event file handler for a non-blocking channel.
set pipe [open "|some command"]
fileevent $pipe readable [list Reader $pipe]
fconfigure $pipe -blocking 0
proc Reader { pipe } {
	if [eof $pipe] {
		catch {close $pipe}
		return
	}
	if {[gets $pipe line] < 0} {
		# We blocked anyway because only part of a line
		# was available for input
	} else {
		# Process one line
	}
}

The fblocked Command

The fblocked command returns 1 if a channel does not have data ready. Normally the fileevent command takes care of waiting for data, so I have only seen fblocked useful in testing channel implementations.

Buffering

By default, Tcl buffers data so I/O is more efficient. The underlying device is accessed less frequently so there is less overhead. In some cases you may want data to be visible immediately and buffering gets in the way. The following turns off all buffering:

fconfigure fileID -buffering none
Full buffering means that output data is accumulated until a buffer fills, then a write is performed. For reading, Tcl attempts to read a whole buffer each time more data is needed. The read-ahead for buffering will not block. The -buffersize parameter controls the buffer size:

fconfigure fileID -buffering full -buffersize 8192
Line buffering is used by default on stdin and stdout. Each newline in an output channel causes an write operation. Read buffering is the same as full buffering. The following command turns on line buffering:

fconfigure fileID -buffering line

End of Line Translations

On UNIX, text lines end with a newline character (\n). On Macintosh they end with a carriage return (\r). On Windows they end with a carriage return, newline sequence (\r\n). Network sockets also use the carriage return, newline sequence. By default, Tcl accepts any of these, and the line terminator can even change within a channel. All of these different conventions are converted to the UNIX style so that once read, text lines always end with a newline character (\n). Both the read and gets commands do this conversion. By default, text lines are generated in the platform-native format during output.

The default behavior is almost always what you want, but you can control the translation with fconfigure. Table 15-4 shows settings for -translation:
End of line translation modes.
binary No translation at all.
lf UNIX-style, which also means no translations.
cr Macintosh style. On input, carriage returns are converted to newlines. On output, newlines are converted to carriage returns.
crlf Windows and Network style. On input, carriage return, newline is converted to a newline. On output, a newline is converted to a carriage return, newline sequence.
auto The default behavior. On input, all end of line conventions are converted to a newline. Output is in native format.

End of File Character

In DOS file systems, there may be a Control-z character (\x1a) at the end of a text file. By default, this character is ignored on the Windows platform if it occurs at the end of the file, and this character is output when you close the file. You can turn this off by specifying an empty string for the end of file character:

fconfigure fileID -eofchar {}

Serial Devices

The -mode attribute specifies the baud rate, parity mode, the number of data bits, and the number of stop bits:

set tty [open /dev/ttya]
fconfigure $tty -mode
=> 9600,0,8,2

Windows has some special device names that always connect you to the serial line devices when you use open. They are com1 through com8. The system console is named con. The null device is nul.

UNIX has names for serial devices in /dev. The serial devices are /dev/ttya, /dev/ttyb, and so on. The system console is /dev/console. The current terminal is /dev/tty. The null device is /dev/null.

Macintosh needs a special command to open serial devices. As of this writing the command is not implemented. Check your Tcl release for a serial command that will open serial devices on all platforms.

Configuring Read-Write Channels

If you have a channel that is used for both input and output, you can set the channel parameters independently for input and output. In this case you can specify a two-element list for the parameter value. The first element is for the input side of the channel, and the second element is for the output side of the channel. If you only specify a single element, it applies to both input and output. For example, the following command forces output end of line translations to be crlf mode, leaves the input channel on automatic, and sets the buffer size for both input and output:

fconfigure pipe -translation {auto crlf} -buffersize 4096


[Top] [Prev] [Next] [Bottom]

welch@acm.org
Copyright © 1997, Brent Welch. All rights reserved.
This will be published by Prentice Hall as the 2nd Edition of
Practical Programming in Tcl and Tk