JavaAsynchronous Event Handling: Get Rid of Nasty Applet Pauses

Asynchronous Event Handling: Get Rid of Nasty Applet Pauses

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.


A responsive user interface is one of the most important considerations in making an application intuitive and friendly. This article describes a common pitfall in event-driven programming that can harm the responsiveness of the user interface, and describes a technique for fixing the problem.

The Problem

Here’s a common error a beginning applet programmer makes. He creates a graphical applet that responds to user input; initially everything is fine.

But the programmer continues to evolve the applet; he modifies the graphics routines, making them do more sophisticated and complicated things. Pretty soon, the applet seems to be ‘hanging’ for extended periods — that is, it’s so busy doing computations or graphics rendering that it doesn’t seem to be responding to user input.

Since the programmer doesn’t know about the one-event-thread rule, he doesn’t know why. And since events are only being delayed, not lost, he might just ignore the problem.

The Cause of the Problem

Here’s the one-event-thread rule: most Java implementations have only a single thread for doing all event processing. If an applet performs a lengthy calculation in response to an incoming event, the user interface may be tied up for the duration, giving the impression that the applet has ‘hung’.

Solution

One solution to this problem is to create separate threads for doing large computations. These threads are dedicated to doing things that take too long to be done in the main event thread.

Using a separate thread to perform something that might have been done in the main thread is sometimes called asynchronous processing, as opposed to synchronous processing.

In our example, we are going to set up a system that is conceptually simple (and later on, we’ll talk about ideas for making it all a bit more sophisticated). Our system is going to spawn a thread to deal with each event. The advantage of this is that we’ll guarantee that the event thread will never be tied up in a long computation.

We’re going to create a class called

STEventThread
. Each time an event comes in, instead of allowing the system to process it normally, we create an

STEventThread
to handle it, and immediately return. All the main event thread has to do is create the thread, and it’s work is done; the time spent doing this is negligible, and so we keep our interface responsive. Meanwhile, the

STEventThread
that we’ve created is going to go off ‘in the background’ and process the event. No matter how long this processing takes, it won’t interfere with the main event thread.

Here’s how you use

STEventThread
:


new STEventThread( this, event );

This single line of code creates a new

STEventThread
, which automatically starts itself running in the background. That’s all you need to do!

But where do you call this? There are a number of plausible answers to this question, but in our example, we are going to override the

processEvent()
method in our applet, like so:


protected void processEvent( AWTEvent event ) {

// Check to see if we are using asynchronous processing
if (asynch) {

// If we are, all we have to do is spawn the thread
// (Asynchronous processing)
new STEventThread( this, event );
} else {

// If not, we carry out the default behavior by calling
// the processEvent() method in our superclass
// (Synchronous processing)
super.processEvent( event );
}
}

Note what we’ve done here. First of all, we make a decision about whether we really want to use asynchronous processing, based on the value taken from an applet parameter. This lets us easily compare the same applet, with and without asynchronous processing, by changing only the value of the parameter.

Assuming we are using asynchronous processing, we create a new

STEventThread
.

Meanwhile, over in

STEventThread
, let’s see what’s happening.


public STEventThread( STEventProcessor step, AWTEvent event ) {
// Store stuff in member variables
this.step = step;
this.event = event;

// Start up a thread
new Thread( this ).start();
}

The constructor is also simple — we stuff our parameters into member variables so we can get at them later, and we start up a thread.

When the thread starts, here’s what happens:


public void run() {
// Process the event
step.stProcessEvent( event );
}

Now, we’re running inside our new, ‘background’ thread. And again, it’s just a tiny bit of code. What we’re doing here is calling back into our applet and resuming the processing of the event. Without further ado, let’s go back to our applet and see what

stProcessEvent()
does.


public void stProcessEvent( AWTEvent event ) {
// Resume normal processing of the event, but from within
// our ‘background’ thread
super.processEvent( event );
}

The reason we need this stub routine is because

Applet.processEvent()
is a protected method, and so we can’t call it directly from

STEventThread
. We’ve defined an interface,

STEventProcessor
, which our applet implements by defining the method

stProcessEvent()
, and it is through this interface that we call this method.

If you forget for a moment about the thread we’ve spawned, this seems like the long way around: instead of allowing the event to be processed by

Applet.processEvent()
, we instead grab the event, pass it over to

STEventThread
, which passes it back to our applet, which then calls

Applet.processEvent()
. This round trip isn’t a waste, however, because when we finally do the actual processing, we are running inside our ‘background’ thread. In fact, that was the point of the whole trip between the classes — we wanted to call

processEvent()
on the event, but in a separate thread.

Let’s compare the journeys of two identical events, one being processed normally, and one being processed by our special setup.

(Note: steps carried out in the main event thread are in blue; steps carried out in a ‘background’ thread are in green.)

Synchronous path

  1. User clicks the mouse button inside the applet
  2. System generates event
  3. System passes event to
    Applet.processEvent()

  4. Event is passed to MouseListener
  5. MouseListener processes event

  6. Applet.processEvent()
    is done

Asynchronous path

  1. User clicks the mouse button inside the applet
  2. System generates event
  3. System passes event to our replacement for
    Applet.processEvent()

  4. Our
    Applet.processEvent()
    creates a new

    STEventThread


  5. Applet.processEvent()
    is done

  6. A new thread is started
  7. In the new thread,
    STEventThread
    passes the event to

    stProcessEvent()


  8. stProcessEvent()
    calls

    Applet.processEvent()

  9. Event is passed to MouseListener
  10. MouseListener processes event

  11. Applet.processEvent()
    is done

Try it out

This page



contains two instances of our example applet. The left instance is set to use synchronous processing, while the right one is set to use asynchronous processing.

If you click on the right applet, you’ll see a white square move across the screen, filling in the space behind it with a new color. If you click a few more times, you see several white squares filling in the applet, each with a new color.

This ‘slow fill’ is our special, slow computation. It takes several seconds to complete, and, thus, if it were implemented naively, would tie up the user interface for those several seconds. However, the applet running on the right side doesn’t have this problem. Each filling process is run in a different ‘background’ thread, and so the applet can continue to process new events while the fill is happening. In fact, this is why you can click several times and see several fills happening at once — if we were using only one thread, the first fill would have to complete before the second one could start. This is what you’ll see if you click on the applet on the left.

Actually, precisely what happens in the left applet depends on what implementation of Java you are using.

In some implementations, you’ll see nothing happen, and then suddenly, the entire window will change to a new color. This is because the left applet is using the main event thread to do the fill, and it just so happens that this thread is also used to respond to

repaint()
requests. Because this thread is tied up, it cannot draw the window, and thus we cannot watch the progress of the fill.

In fact, in some implementations (one example is JDK 1.1.7B for Linux, from Sun), the situation is even worse. The main event thread is shared between the applets, which means that if you are watching the right one do some filling, and you click inside the left one, both applets will appear to be frozen, because the single main event thread is busy doing a fill on the right, which prevents either applet from drawing anything.

In other implementations (for example, Netscape 4.72 for Linux), it seems that each applet has its own event thread. If you click in both applets, the left one will be tied up, and thus will show nothing happening, but the right one continues merrily along doing its fills. Your mileage may vary.

Further considerations

  • If you use asynchronous processing of events, you have to make sure to make the appropriate code thread-safe. This is particularly true if you are converting an existing applet to be asynchronous. It is quite possible that there are data structures and methods that were meant to be used only from a single thread; if you are calling them from ‘background’ threads, you may have to throw some synchronization blocks around some of your code to keep it from breaking.
  • This implementation creates a ‘background’ thread for every single event passed to this Component. As mentioned at the beginning of the article, this is conceptually simple. While asynchronous processing is a good thing for our slow fills, it is a waste of speed and memory for those events which can be handled quickly. Additionally, since multithreaded code is more complex than single-threaded code, we might be making some of our code unnecessarily complex. A solution for this might be to make only certain events asynchronous, allowing others to be processed normally. This choice could be made within our version of
    Applet.processEvent()
    .

Source

About the Author

Greg Travis is a freelance programmer living in New York City. His interest in computers can probably be traced back to that episode of “The Bionic Woman” where Jamie runs around trying to escape a building whose lights and doors are controlled by an evil artificial intelligence that mocks her through loudspeakers. He’s a devout believer in the religious idea that, when a computer program works, it’s a complete coincidence. He can be reached at mito@panix.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories