Flexible Event Delivery with Executors
by Andrew Thompson03/23/2005
Every Java programmer is familiar with the EventListener pattern
of asynchronous event delivery. Most have also written the boilerplate code
needed to manage listeners and deliver events to other components. Listeners
are simple, familiar, flexible, and easy to implement, but they call into code
written by other developers, which can cause problems:
- A slow listener can take too long to handle an event, starving the other listeners and possibly deadlocking your component.
- When you dispatch an event to listeners, you control which thread will deliver it. Typically, the developers who implement the listeners have no control over how events are delivered to their code.
- In a multimedia application, it is not clear how to synchronize GUI events (like animation and user actions) with other asynchronous event streams (like speech, sound, and video).
This article uses Doug Lea's Executor (now
part of J2SE 5.0) to
make event dispatch more flexible. You can use the ideas presented to:
- Allow the developer using your component to plug in event dispatch strategies that customize how events are delivered.
- Isolate different listeners from each other, so one bad listener will not be able to starve the others.
- Multiplex streams of events from different asynchronous sources.
A note on Java 1.4 compatibility: This article uses J2SE
5.0 generic types to eliminate casting of events and listeners. The Dispatchable
Event Library also contains a non-generic version of the classes that
will run on JDK 1.4.
Older JDKs lack a built-in java.util.concurrent package, but
fortunately you can download
a compatible back-ported version.
Standard Listeners
For the examples in this article, imagine writing a video editing system that
uses a ClipEvent class to report video clip events (e.g., started,
paused, etc.) and a ClipListener interface implemented by components
that respond to the events.
import java.util.EventListener;
class ClipEvent extends EventObject {
//...
}
public interface ClipListener
extends EventListener {
public void clipUpdate(ClipEvent e);
}
In many programs, events are created by some kind of controller class that
is also responsible for managing a list of interested listeners and dispatching
each event that occurs to the listeners. Often, it is possible to split this
responsibility and delegate listener management to a simple helper class called
a dispatcher. ClipEventDispatcher illustrates this style of event
dispatch:
import java.util.*;
public class ClipEventDispatcher {
final Collection listeners = new ArrayList();
public synchronized void
addListener(ClipListener l) {
listeners.add(l);
}
public synchronized void
removeListener(ClipListener l) {
listeners.remove(l);
}
public void fireEvent(ClipEvent e) {
for(Iterator i = copyListeners(); i.hasNext();) {
ClipListener l = (ClipListener) i.next();
l.clipUpdate(e);
}
}
private synchronized Iterator copyListeners() {
return new ArrayList(listeners).iterator();
}
}
ClipEventDispatcher exhibits the typical event delivery problems
discussed in the introduction. If any ClipListener has a slow
implementation of the clipUpdate method the other listeners starve.
The dispatcher's author decides which Thread will call fireEvent;
there is no way for the developer of a ClipListener to customize
event delivery.
Flexible Task Execution in Java: The Executor Interface
J2SE 5.0 standardizes
the java.util.concurrent package, which includes the Executor interface
originally created by Doug Lea. Executors run tasks that implement
the java.lang.Runnable interface. You are probably familiar with
using Runnable tasks in conjunction with java.lang.Thread:
class MyCoolTask implements Runnable {
public void run() {
//... do useful stuff
}
}
Thread t = new Thread(new MyCoolTask());
t.start();
Using Runnables with an Executor is similar:
Executor e = ...
e.execute(new MyCoolTask());
Runnable tasks passed to the execute method will have
their run method called by the Executor. Unlike a Thread,
which can only have its start method called once, Executors
can typically run as many Runnable tasks as you need. Different Executors
embody different strategies for executing the tasks. For example, J2SE
5.0 provides an Executor that acts as a scheduler, meaning it
calls the run method of a task after a configurable time delay.
The Useful Executors section on the next page describes several Executors in
more detail, but first we will learn how to apply them to our event dispatch
problems. Adding Flexibility with DispatchableEvent
To make it easy to combine Executors and events I have developed
a Dispatchable
Event Library that offers a helper class, DispatchableEventSupport,
which provides listener management and event dispatch. Internally, a DispatchableEventSupport instance
uses an Executor to fire events, and thus changing the Executor customizes
the event delivery strategy.
Here is the ClipEventDispatcher example rewritten using the DispatchableEvent library:
import org.recoil.pixel.dispatchable.*;
import org.recoil.pixel.executor.*;
public class ClipEventDispatcher {
Executor e = new DirectExecutor(); //[1]
DispatchableEventSupport<ClipListener> d =
new DispatchableEventSupport<ClipListener>(e);
public void addListener(ClipListener l) {
d.addListener(l);
}
public void removeListener(ClipListener l) {
d.removeListener(l);
}
public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
<ClipListener, ClipEvent>(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce); //[2]
}
});
}
}
At line [1] this example uses
a DirectExecutor, which simply recreates the behavior of the original ClipEventDispatcher.
The event delivery can be customized by varying the Executor used,
either when the DispatchableEventSupport is created or when listeners
are added.
Line [2] illustrates how little code
you need to integrate with your application. The Dispatchable Event Library
handles the mechanics of event dispatch, usually all you need to do is invoke
your callback method (e.g., clipUpdate).
Dispatchable Event Library Details
The Dispatchable Event Library contains several useful helper classes able
to dispatch any kind of event. The key classes are found in the package org.recoil.pixel.dispatchable:
DispatchableEventDispatcher-
Fires events using an
Executor, but does not provide listener management. Useful when you want to add flexibility to existing event dispatch code. DispatchableEventSupport-
Most applications will want to use this main helper class, which extends
DispatchableEventDispatcherwith listener management. It should be familiar if you knowjava.beans.PropertyChangeSupport. PropertyChangeEventDispatcher-
Combines
PropertyChangeSupportwithDispatchableEventDispatcher, providing flexible dispatch forPropertyChangeEvents. This is a good example to study to learn how to integrateDispatchableEventswith existing code. DispatchableEvent-
Abstract class that you extend with your event delivery code.
Pages: 1, 2 |