The Source for Java Technology Collaboration
User: Password:



Richard Bair

Richard Bair's Blog

Swing and Non Blocking JAX-WS

Posted by rbair on June 27, 2006 at 04:12 PM | Comments (5)

Yesterday I blogged about JAX-WS and how to use it with Swing. I tried to sneak by with a simple example that happened to block the EDT during the web service invocation (bad Richard! bad). Since I was called out on it, I took the opportunity to create a new JavaBean for working with background tasks and update my example to use it.

The problem with yesterday's example was this method:


    private void generateButtonActionPerformed(java.awt.event.ActionEvent evt) {                                         
        randomNumbersWS.setCount((Integer)countSpinner.getValue());
        final List results = randomNumbersWS.generateRandomNumbers();
        resultsList.setModel(new AbstractListModel() {
            public Object getElementAt(int index) {
                return results.get(index);
            }
            public int getSize() {
                return results.size();
            }
        });        
    }                                        

In this method I contact the webservice (a potentially lengthy process) while in the Event Dispatch Thread. In other words, while trying to contact the web service the entire GUI was frozen. Solid. Today we'll fix the problem by using a background thread.

For this task I created a new non visual JavaBean called BackgroundWorker. For those familiar with .NET, you'll notice the name. BackgroundWorker is a helper class that handles a lot of the threading work for you. BackgroundWorker is intended to be used in your GUI builder environment. To use it, you:

  • simply drag and drop a BackgroundWorker from the development palette
  • go to the property sheet and select the doInBackground event
  • write the code for your background task. Call "process" on the BackgroundWorker to publish whatever data you want to in the GUI
  • go to the property sheet and select the "publish" event
  • display the data in the gui (the data is part of the BackgroundEvent)

Those who've written multithreaded Swing code in the past have probably used SwingWorker. SwingWorker is an excellent piece of code, and handles a lot of the really sticky details for multithreading. BackgroundWorker, in fact, delegates to SwingWorker to do the actual multi-threading work. Why write BackroundWorker then? Well, when using a GUI builder, using a non visual component like BackgroundWorker can really be nice. I've found it much easier to "view" code visually in a GUI builder than to read and mentally visualize code.

Here's the new code, using a JXStatusBar and the BackgroundWorker. Oh, and by the way, I also implement the "started" and "done" events to make sure my GUI is properly disabled and re-enabled during this long process.


    private void getNumbersWorkerPublish(org.jdesktop.swingx.event.BackgroundEvent evt) {                                         
        final List results = (List)evt.getData();
        resultsList.setModel(new AbstractListModel() {
            public Object getElementAt(int index) {
                return results.get(index);
            }
            public int getSize() {
                return results.size();
            }
        });        
    }                                        

    private void getNumbersWorkerDoInBackground(org.jdesktop.swingx.event.BackgroundEvent evt) {                                                
        //ok, go and get the numbers. Call process when done fetching the numbers
        randomNumbersWS.setCount((Integer)countSpinner.getValue());
        final List results = randomNumbersWS.generateRandomNumbers();
        getNumbersWorker.process(results);
    }                                               

    private void getNumbersWorkerFinished(org.jdesktop.swingx.event.BackgroundEvent evt) {                                          
        //enable the button, update the status bar
        generateButton.setEnabled(true);
        statusLabel.setText("Ready");
    }                                         

    private void getNumbersWorkerStarted(org.jdesktop.swingx.event.BackgroundEvent evt) {                                         
        //disable the button, update the status bar
        generateButton.setEnabled(false);
        statusLabel.setText("Loading numbers from remote server...");
    }                                        

    private void generateButtonActionPerformed(java.awt.event.ActionEvent evt) {                                               
        getNumbersWorker.execute();
    }                                              

And here's the code for BackgroundWorker (I've omitted the event classes because they're boring :-). If there is enough interest, I'll push this code into SwingX).


/*
 * BackgroundWorker.java
 *
 * Created on June 27, 2006, 9:02 AM
 */

package org.jdesktop.swingx;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import org.jdesktop.swingx.event.BackgroundEvent;
import org.jdesktop.swingx.event.BackgroundListener;
import org.jdesktop.swingx.util.SwingWorker;

/**
 *
 * @author rbair
 */
public class BackgroundWorker extends JavaBean {
    private List listeners = new ArrayList();
    private SwingWorker delegate;
    
    /** Creates a new instance of BackgroundWorker */
    public BackgroundWorker() {
    }

    public void execute() {
        //if another task is already executing, fire and IllegalStateException
        if (delegate != null) {
            throw new IllegalStateException("A background task is already in progress");
        }
        
        //create a new delegate
        synchronized(this) {
            delegate = new DelegateWorker();
            fireStartedEvent();
            delegate.execute();
        }
    }
    
    public void process(Object... chunks) {
        for (Object data : chunks) {
            firePublishEvent(data);
        }
    }
    
    public void addBackgroundListener(BackgroundListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }
    
    public void removeBackgroundListener(BackgroundListener listener) {
        listeners.remove(listener);
    }
    
    public BackgroundListener[] getBackgroundListeners() {
        return listeners.toArray(new BackgroundListener[0]);
    }
    
    private void cleanup() {
        delegate = null;
    }
    
    private void fireStartedEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.started(evt);
        }
    }
    
    private void fireDoneEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.finished(evt);
        }
    }
    
    private void fireDoInBackgroundEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.doInBackground(evt);
        }
    }
    
    private void firePublishEvent(Object data) {
        BackgroundEvent evt = new BackgroundEvent(BackgroundWorker.this, data);
        for (BackgroundListener listener : listeners) {
            listener.publish(evt);
        }
    }
    
    private final class DelegateWorker extends SwingWorker {
        protected Object doInBackground() throws Exception {
            fireDoInBackgroundEvent();
            return null;
        }

        protected void done() {
            cleanup();
            fireDoneEvent();
        }
    }
}


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • I wanted to mention, I've been working with Igor on some enhancements to BackgroundWorker, so don't be surprised if the API changes some.

    Posted by: rbair on June 27, 2006 at 04:32 PM

  • That's so funny. I wrote a class just about like this the other day. Yours of course is better. Please do put the finished code somewhere!
    Also, just wanted to mention, I've been using Spin successfully for most of my background thread work. In my opinion, it simplifies the code required to create a background task.Adam

    Posted by: bobsledbob on June 28, 2006 at 11:31 AM

  • Adam: Very interesting. I haven't looked into Spin before. Thanks for the link!

    Posted by: rbair on June 28, 2006 at 01:28 PM

  • Adam: You bet. I'm cleaning up the BackgroundWorker now. I'll post something here once it is finished.

    Posted by: rbair on June 28, 2006 at 02:07 PM

  • I've had good success with FoxTrot (http://foxtrot.sourceforge.net/docs/introduction.php) along with DynamicProxy for this kind of task. (See http://www.futuretek.com/presents/foxtrot/KeepingYourSwingApplicationsResponsiveUsingFoxTrot.ppt)

    Fox has the notion of concurrent and single worker models that gives you the flexibility of allowing multiple outstanding synchronous calls from the EDT.

    Posted by: rrr6399 on November 12, 2007 at 04:29 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds