3

All,

We are having problems with data loading in our application. The purpose of the current implementation was to have an asynchronous mechanism for data loading.

I basically want to know if there are any good patterns or practices for this.

The application has many different UI components that is not coded in a standard way. It is all generated from configuration (i.e. xml). The data load is triggered by clicking on a calendar, which would send an asynchronous message to the data loaders, of which there are around 6 instances (of the same class) which would load the data from the database. Once the data has been loaded and processed, the data loader class would then send the table data asynchronously to the JTable it is to be displayed in.

As the data load can take anywhere from a second or so to 1 or 2 minutes, a progress dialog was required. An additional class was written to listen for events from the data loaders when the start/complete the data load. When any data loader starts loading, a dialog is displayed indicating that the data was loading. When the last data loader had completed, the dialog should be hidden.

Current Problems/Issues

There are 2 problems that occur:

  • Sometimes the dialog will remain visible, even though all of the data loaders have completed.
  • Othertimes, the data may not be displayed in the table.

We have tried in vain to add the synchronized keyword around the showing and hiding methods of the progress, but still get similar issued. I think some kind of rewrite may be in order, but I want to understand a little more on async java patterns. The mechanism needs to be more robust and reliable.

Current Process

If your interested in our current process, I have tried to summarise the events below. This might shed some light on problems in design/implementation.

  1. User clicks on day in calendar.

    The calendar component sends a message to each data loader instance telling them to start loading. This message is sent from the calendar asynchronously on a separate thread (2).

  2. The Data Loader receives the load message

    Each DataLoader is responsible for loading relevant data and passing it to a table to be displayed.

    Each data loader has a single thread instance on which the data is loaded. If the current thread load instance is set and is loading data, then the loading is aborted - which might occur from a quick click of days in the calendar. A new thread instance is created and started once the data loading thread is idle or null.

    The data load thread then performs the data access and processing. When it is started, the DataLoadDialog class is notified (3). This DataLoadDialog basically adds a listener (DataLoaderListener extends EventListener) to a EventListenerList. When the data thread loads, the DataLoaderListener.loadStart is invoked, which is picked up by the DataLoadDialog. When the thread has loaded and processed the data, the DataLoaderListener.loadComplete is called. This is again picked up in the DataLoadDialog.

    Now that the data is loaded, the DataLoader sends the data to the table to be displayed.

  3. Inform DataLoadDialog of load status

    The DataLoadDialog has a reference to each data loader. Each data loader has a status, which is accessed via a getter, which returns an enum of Idle or Loading. Whenever the DataLoadDialog receives an invokation of the loadStart or loadComplete, it determines whether or not to show/hide the progress dialog, depending upon the state of the data load threads.

    The process dialog is a singleton static instance which is created when the class in created (from the configuration xml).

Any help would be appreciated. If I have missed anything then please add comment and I will update the question.

Thanks,

Andez

kleopatra
  • 51,061
  • 28
  • 99
  • 211
Andez
  • 5,588
  • 20
  • 75
  • 116

3 Answers3

3

The problems sound like violations of the base Swing rule: all component access must happen on the EDT. Make sure all user feedback (showing/hiding the dialog, updating the process state, updating table data) is triggered on the EDT.

A useful class helping to do so is SwingWorker, its api doc is quite extensive and has example of how to access models/components directly.

Some pseudo-code snippets showing how to decouple the worker a bit from the actual ui by passing intermediate data via a propertyChangeEvent:

// on user click (here we are on EDT), 
// open the dialog, create the loader, add a PropertyChangeListener and start 
showProcessDialog();
MySwingWorker worker = new MySwingWorker();
PropertyChangeListener l = new PropertyChangeListener() {
     public void propertyChanged(....) {
         if (StateValue.DONE == evt.getNewValue) {
             closeProcessDialog();
         }
         if ("chunkAvailable".equals(evt.getPropertyName()) {
             addChunkToTable((ChunkType) evt.getNewValue());
         }
     }    
}
worker.addPropertyChangeListener(l);
worker.execute();

// custom implementation of SwingWorker
public MySwingWorker extends SwingWorker<ResultType, ChunkType> {

    @Override
    protected  <ResultType> doInBackground() {
         // here we are in the worker thread 
         // start the actual loaders 
         Dataloaders loaders = ....
         // process their result/s 
         ResultType endResult = ...;
         while (...) {
             ChunkType intermediateResult = ....
             // possibly produce some end result
             endResult = ... 
             // publish the intermediate chunk 
             publish(intermediateResult); 
         }
         return endResult; 
    }

    @Override
    protected void process(List<ChunkType> chunks) {
         // here we are on the EDT, so it's safe to either notify swing listener
         // or access swing components/models directly (as shown in the api/tutorial example) 
         for(ChunkType chunk: chunks) {
             PropertyChangeEvent e = new PropertyChangeEvent(this, "chunkAvailable", null, chunk);
             getPropertyChangeSupport().firePropertyChange(e);
         } 
    }

}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Thanks kleopara. Will look into it. I have heard the SwingWorker mentioned before, just need to get my head around it and how it could fit into our application as a whole :-S – Andez Jan 09 '12 at 12:06
  • @kleopatra: can I add something? About SwingWorker used, it's kindda bit strange but after I do debugging using Netbeans... THere are too many SwingWorker-pool-thread alive, is this what it supposed to be happened? – gumuruh Jun 05 '12 at 15:15
2

In addition to @kleopatra's cogent suggestions, the article cited here discusses ways to find EDT violations in existing code. More examples here and here.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
0

I looked into the SwingWorker. I worked through the examples, but the idea would be to spawn off each DataLoader from the SwingWorker - I think.

I decided against doing this and found a problem with the way in which the interaction between the DataLoadDialog class and the Swing JDialog progress. I had simplified my example too much. There were 2 additional combos that also triggered the data load. One combo would force 3 of the data loaders to start loading, and the other combo for another 3 data loaders. That would have force me to add extra configuration which would be awkward due to the way the system is written.

I had initially tried adding the synchronized keyword to the loadStart and loadComplete which still did not fix the problem. I also had invokeAndWait's in place where the dialog was displayed/hidden. I hoped that this would prevent multiple threads trying show/hide the progress - but it didn't. I replaced the invokeAndWait with invokeLater which seemed to do the trick.

I totally agree with SwingWorker, scenarios like this should be laid out more in design - which we are bad at here - which I hate. The whole concurrent solution was put together after it took around 20 minutes to load the initial data synchronously - 1 loader retrieving data from the database in turn. The amount of data loaded is quite horrendous - and it also had to run through some kind of conditioning.

Andez

Andez
  • 5,588
  • 20
  • 75
  • 116