The Tcl Event Loop


About:

JBlend supports evaluation of events in the Tcl event loop. It is important that developers understand how the Tcl event loop works and how it should be used. A number of Tcl features depend on the Tcl event loop, for example vwait will not work properly if Tcl events are not being processed. The event loop also implements thread synchronization in Tcl, so it must be used correctly in a multi-threaded environment like Java.

The tcl.lang.Notifier class implements event loop processing functionality in JBlend. There is one Notifier object per-thread. A Thread can contain 1 to N Tcl interpreters, each interpreter in a specific thread will have the same Notifier.

In the most simple case, a Thread would contain a single Tcl interpreter and a Notifier object.


The Notifier manages the Tcl event queue, so if the interpreter were to invoke after commands like the following:

after 100 {puts hi}
after 200 {puts bye}

The result would look like:

Tcl events are processed using the Notifier.doOneEvent() API. Typically, event processing is done in a loop and the doOneEvent() method is invoked over and over again.


import tcl.lang.*;

public
class EventProcessingThread implements Runnable {
    Interp interp;

    public void run() {
        interp = new Interp();

        try {
            while (true) {
                interp.getNotifier().doOneEvent(TCL.ALL_EVENTS);
            }
        } finally {
            interp.dispose();
        }
    }
}

The example above does not queue any events, so when the doOneEvent() method is invoked the thread will block waiting for an event to process. A developer might want to setup an interp like this and then queue up events to be processed from another thread. The following example shows how a developer might source a Tcl file and invoke a Tcl proc defined in that file.


import tcl.lang.*;

public
class InterpThreadSetup {

    public static
    String evalInOtherThread() {
        EventProcessingThread r = new EventProcessingThread();
        Thread t = new Thread(r);
        t.start();

        // Wait for other Thread to get ready
        while (r.interp == null) {
            try {Thread.sleep(10);} catch (InterruptedException e) {}
        }
        final Interp interp = r.interp;
        final StringBuffer result = new StringBuffer();

        TclEvent event = new TclEvent() {
            public int processEvent(int flags) {
                try {
                    interp.eval("source somefile.tcl");
                    interp.eval("cmd");
                    result.append( interp.getResult().toString() );
                } catch (TclException ex) {
                    // Handle Tcl exceptions here
                }
                return 1;
	    }
        };

        // Add event to Tcl Event Queue in other thread
        interp.getNotifier().queueEvent(event, TCL.QUEUE_TAIL);

        // Wait for event to be processed by the other thread.
        event.sync();

        return result.toString();
    }
}

The example above creates a EventProcessingThread object and then waits for it to get ready. The EventProcessingThread will start and then block inside the doOneEvent() method. A new inner class that extends TclEvent is then created and added to the Tcl event queue via interp.getNotifier().queueEvent(). Finally, the current thread is blocked waiting for the EventProcessingThread to process the event. When the processEvent method is invoked by the EventProcessingThread, a Tcl file will be sourced and a Tcl proc named cmd will be invoked. These two threads would look like the following:

While the example above may seem complex, each step is required to ensure that events are processed in the correct order and that Tcl commands are invoked in a thread safe way. Readers should note that it is not legal to invoke interp.eval() directly from a thread other than the one processing TclEvents via doOneEvent(). To do so will cause a crash or random exceptions. In the example above, the interp.eval() method is invoked inside the the processEvent method and that method is invoked by a call to doOneEvent() in EventProcessingThread. Using this approach, any number of threads can queue Tcl events and they will be processed in the correct order.

Multiple Interpreters:

A single thread can contain 1 to N interpreters. A developer could create multiple Tcl interpreters in Java code via the Interp() constructor. A developer could also use the interp command inside Tcl to create a "child" interp. The following example uses the interp create command to show how events in two different interpreters are processed by the same Notifier.

set i2 [interp create i2]

$i2 eval {
set name "i2"
after 100 {puts "hi $name"}
after 200 {puts "bye $name"}
}

set name "main"
after 100 {puts "hi $name"}
after 200 {puts "bye $name"}

The code above would result in a thread containing two Tcl interpreters, it would look like the following:

Both interpreters share the same Notifier object, so events in the Tcl Event queue for this thread would be processed in the following order:

Tcl Event Queue

after 100 {puts "hi $name"}  (in i2 interp)
after 100 {puts "hi $name"}  (in main interp)
after 200 {puts "bye $name"} (in i2 interp)
after 200 {puts "bye $name"} (in main interp)

The output of the above code would look like:

hi i2
hi main
bye i2
bye main

Copyright © 1997-1998 Sun Microsystems, Inc.