Swing Worker Example

1 year ago by in Articles, Concurrency, Swing Tagged: , , , ,

Java provides a neat way to carry out long lasting jobs without have to worry about the complexity of threads or lack of responsiveness in an application (by application we mean Swing applications). It is called SwingWorker (Java Doc). It is not the latest thing on Earth (released with Java 1.6) and you may have already read about it. In this article we will see how to use it and why it was created to start with.

All code listed below is available at: https://java-creed-examples.googlecode.com/svn/swing/Swing Worker Example/. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.

Why do we need the SwingWorker?

Say we need to create an application that performs some task that take a considerable amount of time to complete. Downloading a large file or executing a complex database query are good examples of such tasks. Let us assume that these tasks are triggered by the users using a button. With single threaded applications, the user clicks the button that starts the process and then has to wait for the task to finish before the user can do something else with the application.

Single Thread

Single Thread

The application will become unresponsive as the only thread available is carrying the long task. The user cannot even cancel the task halfway through neither, as the application is busy carrying the long task. The application will become responsive only when the long task is finished. Unfortunately many applications manifest such behaviour and users get frustrated as there is no way to cancel such task or interact with the application before this long task is complete.

Multithreading address this problem. It enables the application to have the long tasks executed on a different thread. The application handles small tasks, such as button clicks, by one thread and the long taking tasks by another thread.

Two Threads

Two Threads

This figure shows only two threads, but an application can have more than just two threads. One can say that threads solved this problem and this is true. But threads create new challenges. The figure above shows two threads that work independently, that is, one thread does not communicate or exchange data with the other thread. This may not always be the case and threads may need to share data.

Swing, like many other GUI frameworks, makes use of what is called thread confinement. What is this? All Swing objects are handled by one thread only, the event dispatcher thread. We just agreed that multithreading yields more responsive applications, then why Swing uses one thread? After several attempts (not only in Java but in many other languages too such as C++) in making GUI components multithreaded, it was decided to handle all GUI objects (such as buttons, lists, models and the like) by one thread. All multithreaded GUI prototypes suffer from deadlocks due to reasons which are beyond the scope of this article. For more information about this, please refer to this article.

Therefore all Swing objects must be accessed only from the event dispatcher thread.

This leads back to the original problem. We cannot share Swing objects with other threads outside the event dispatcher thread. This is why we start a Swing application as shown below.

package com.javacreed.examples.swing.worker.part1;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Main {
  public static void main(final String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        final JFrame frame = new JFrame();
        frame.setTitle("Test Frame");
        frame.setSize(600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
      }
    });
  }
}

Here we are instructing Java to execute our Runnable (Java Doc) by the event dispatcher thread. Therefore the JFrame (Java Doc) exists only within this thread. If all events are handled by one thread, then that thread will be block when we add long lasting tasks to the queue. The SwingWorker offers a neat solution to this problem as we will see in this article.

SwingWorker

SwingWorker is an abstract class which hides the threading complexities from the developer. It is an excellent candidate for applications that are required to execute tasks (such as retrieving information over the network/Internet or other slow sources) which may take some time to finish. It is ideal to detach such tasks from the application and simply keep an eye on their progress and provide means for cancelling them.

The following example illustrates a simple empty worker that will return/evaluate to an integer when the given task is finished. It will inform the application with what’s happening using objects of type string, basically text messages.

package com.javacreed.examples.swing.worker.part2;

import java.util.List;

import javax.swing.SwingWorker;

public class MyBlankWorker extends SwingWorker<Integer, String> {

  @Override
  protected Integer doInBackground() throws Exception {
    // Start
    publish("Start");
    setProgress(1);
    
    // More work was done
    publish("More work was done");
    setProgress(10);

    // Complete
    publish("Complete");
    setProgress(100);
    return 1;
  }
  
  @Override
  protected void process(List<String> chunks) {
    // Messages received from the doInBackground() (when invoking the publish() method)
  }
}

Let’s understand the anatomy of the SwingWorker class.

  • The SwingWorker class provides two placeholders (generics). The first one represents the type of object returned when the worker has finished working. The second one represents the type of information that the worker will use to inform (update) the application with its progress, and is highlighted in the following example.
    public class MyBlankWorker extends SwingWorker<Integer, String> {
    
      @Override
      protected Integer doInBackground() throws Exception {
        // Start
        publish("Start");
        setProgress(1);
        
        // More work was done
        publish("More work was done");
        setProgress(10);
    
        // Complete
        publish("Complete");
        setProgress(100);
        return 1;
      }
      
      @Override
      protected void process(List< String> chunks) {
        // Messages received from the doInBackground() (when invoking the publish() method)
      }
    }
    
  • The SwingWorker class also provides means to update the progress by means of an Integer which has nothing to do with the two generics mentioned before. This is managed through the setProgress() method which take an integer between 0 and 100 both inclusive.
  • The method doInBackground() is where the complex and long task is executed. This method is not invoked by the event dispatcher thread, but by another thread (referred to hereunder as the worker thread). From this method we can update the progress using either the publish() method and/or the setProgress(). Here something very important happens. The invocations made by these two methods will add small tasks to the event dispatcher thread creating a one-way bridge between the thread that is doing the work and the event dispatcher thread.

    Observation

    The doInBackground() may invoke the publish() method and/or the setProgress() too often and may flood the event dispatcher thread queue. In order to mitigate this problem, the small tasks may be suppressed or coalesced. Therefore several requests may result in to one small task in the event dispatcher thread queue.
  • The method publish() is invoked several times from within the doInBackground() method and works together with the process(). Further to what was described above, the publish() method is invoked from the worker thread, while the process() is invoked by the event dispatcher thread. The following image illustrates this.

    Relation between the publish and process methods

    Relation between the publish and process methods

    The long grey bar represents the doInBackground() method while the small red bars within it, represents the invocation of the publish() method. As shown above, these requests may be coalesced into one request on the even dispatcher thread. The two green bars represent the number of times the process() method is invoked. In this example, the publish() method is invoked a total of 12 times, whereas the process() method is invoked only twice. This also provides a performance boost as the event dispatcher thread is nor overloaded with too many small requests.

    Furthermore, the process() method is invoked asynchronously on the event dispatch thread. The above image shows this clearly and we have no control to when this is actually invoked. It depends on the amount of work that the event dispatcher thread has.

    For example:

     publish("a");
     publish("b", "c");
     publish("d", "e", "f");
    

    may result in:

     process("a", "b", "c", "d", "e", "f")
    

    Observation

    Since the process() method is invoked on the event dispatcher thread, we can access and modify the state of Swing components without having to worry about compromising the thread-safety. We do not have to worry about thread-safety when using the Swing Worker as this has been dealt with by the same SwingWorker on our behalf.
  • The setProgress() works very similar to what was described above, with one difference. The application needs to register a listener in order to receive progress notifications. This will be explored in more detail later on in this article.

Here we have described the functionality of each major block and the role these blocks play. Now we will see a practical example of how we can utilise the SwingWorker within an application.

SwingWorker Example

Let say, for example, we need to find the number of occurrences of a given word (or phrase) with in some text documents located under a directory as shown in the following application.

Search Word Application

Search Word Application

This application allows the user to provide the word to search and the path where to look for. Once the user clicks the search button, the Swing worker starts searching in the background. Thus it frees the event thread avoiding the application from freezing. While waiting for the results, the user can cancel the task by pressing the cancel button that is shown instead of the search button.

The swing working is a good candidate for such a problem. The files listing and searching is quite a long task and can take a couple of seconds (if not minutes) to complete. If this task is performed by the event dispatcher thread, then this thread will not be able to do anything else until this task is complete. The user will not be able to cancel the task.

The following swing worker performs all the work we need.

package com.javacreed.examples.swing.worker.part3;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTextArea;
import javax.swing.SwingWorker;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang.StringUtils;

/**
 * Searches the text files under the given directory and counts the number of instances a given word is found
 * in these file.
 * 
 * @author Albert Attard
 */
public class SearchForWordWorker extends SwingWorker<Integer, String> {

  private static void failIfInterrupted() throws InterruptedException {
    if (Thread.currentThread().isInterrupted()) {
      throw new InterruptedException("Interrupted while searching files");
    }
  }

  /** The word that is searched */
  private final String word;

  /**
   * The directory under which the search occurs. All text files found under the given directory are searched.
   */
  private final File directory;

  /** The text area where messages are written. */
  private final JTextArea messagesTextArea;

  /**
   * Creates an instance of the worker
   * 
   * @param word
   *          The word to search
   * @param directory
   *          the directory under which the search will occur. All text files found under the given directory
   *          are searched
   * @param messagesTextArea
   *          The text area where messages are written
   */
  public SearchForWordWorker(final String word, final File directory, final JTextArea messagesTextArea) {
    this.word = word;
    this.directory = directory;
    this.messagesTextArea = messagesTextArea;
  }

  @Override
  protected Integer doInBackground() throws Exception {
    // The number of instances the word is found
    int matches = 0;

    /*
     * List all text files under the given directory using the Apache IO library. This process cannot be
     * interrupted (stopped through cancellation). That is why we are checking right after the process whether
     * it was interrupted or not.
     */
    publish("Listing all text files under the directory: " + directory);
    final List<File> textFiles = new ArrayList<>(FileUtils.listFiles(directory, new SuffixFileFilter(".txt"),
        TrueFileFilter.TRUE));
    SearchForWordWorker.failIfInterrupted();
    publish("Found " + textFiles.size() + " text files under the directory: " + directory);

    for (int i = 0, size = textFiles.size(); i < size; i++) {
      /*
       * In order to respond to the cancellations, we need to check whether this thread (the worker thread)
       * was interrupted or not. If the thread was interrupted, then we simply throw an InterruptedException
       * to indicate that the worker thread was cancelled.
       */
      SearchForWordWorker.failIfInterrupted();

      // Update the status and indicate which file is being searched. 
      final File file = textFiles.get(i);
      publish("Searching file: " + file);

      /*
       * Read the file content into a string, and count the matches using the Apache common IO and Lang
       * libraries respectively.
       */
      final String text = FileUtils.readFileToString(file);
      matches += StringUtils.countMatches(text, word);

      // Update the progress
      setProgress((i + 1) * 100 / size);
    }

    // Return the number of matches found
    return matches;
  }

  @Override
  protected void process(final List<String> chunks) {
    // Updates the messages text area
    for (final String string : chunks) {
      messagesTextArea.append(string);
      messagesTextArea.append("\n");
    }
  }
}

Note that the swing worker takes a swing component (JTextArea) as an input to its constructor. This swing component is only accessed from the process() method and never used from within the doInBackbround() method or other methods directly (by directly we mean from the same thread) invoked from it. The following image highlights the places from where the swing component is accessed.

Accessing Swing Components from the Swing Worker

Accessing Swing Components from the Swing Worker

This is very important as all swing components should be only accessed from the event dispatcher thread.

Now we saw how to create a swing worker. In the next sections we will see how to start the worker and how to stop or better cancel the worker.

Starting the Swing Worker

The swing worker can be started by invoking the execute() method as highlighted in the following example.

  private void search() {
    final String word = wordTextField.getText();
    final File directory = new File(directoryPathTextField.getText());
    messagesTextArea.setText("Searching for word '" + word + "' in text files under: " + directory.getAbsolutePath()
        + "\n");
    searchWorker = new SearchForWordWorker(word, directory, messagesTextArea);
    searchWorker.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(final PropertyChangeEvent event) {
        switch (event.getPropertyName()) {
        case "progress":
          searchProgressBar.setIndeterminate(false);
          searchProgressBar.setValue((Integer) event.getNewValue());
          break;
        case "state":
          switch ((StateValue) event.getNewValue()) {
          case DONE:
            searchProgressBar.setVisible(false);
            searchCancelAction.putValue(Action.NAME, "Search");
            try {
              final int count = searchWorker.get();
              JOptionPane.showMessageDialog(Application.this, "Found: " + count + " words", "Search Words",
                  JOptionPane.INFORMATION_MESSAGE);
            } catch (final CancellationException e) {
              JOptionPane.showMessageDialog(Application.this, "The search process was cancelled", "Search Words",
                  JOptionPane.WARNING_MESSAGE);
            } catch (final Exception e) {
              JOptionPane.showMessageDialog(Application.this, "The search process failed", "Search Words",
                  JOptionPane.ERROR_MESSAGE);
            }

            searchWorker = null;
            break;
          case STARTED:
          case PENDING:
            searchCancelAction.putValue(Action.NAME, "Cancel");
            searchProgressBar.setVisible(true);
            searchProgressBar.setIndeterminate(true);
            break;
          }
          break;
        }
      }
    });
    searchWorker.execute();
  }

This will create a new thread from where the doInBackground() method is invoked.

Cancel the Worker

The swing worker can be stopped or better cancelled through the cancel() method. The swing worker provides a method called cancel which accepts a parameter of type boolean. This parameter determines whether or not the worker should be interrupted or not.

 
  private void cancel() {
    searchWorker.cancel(true);
  }

This will cause the worker’s get method to throw the exception CancellationException to indicate that the worker was forced cancellation.

Conclusion

Many GUI applications can be easily improved by taking advantage of the swing worker as we saw here. Any tasks that may take long time to execute, should always be performed through a swing worker as these will hinder the application responsiveness. Note that while testing, a developer may use a small search space which takes no time to execute, while in production the application will make use of a larger search space which will take longer to execute. Thus may make the application unresponsive until the long task is finished. The swing worker is not difficult to use and incorporate with existing code as shown in this article.

Albert Attard

Albert Attard is a Java passionate and technical lead at a research group. You can find him on . Over the past years Albert worked on various Java projects including traditional server/client applications, modular applications, large data handling applications and concurrent data manipulation applications to name a few. He has a BSc degree from the University of London (Homepage) and an MSc Information Security with the same university. His MSc thesis (Book) received the 2012 SearchSecurity.co.UK award (Website).

20 Responses to “Swing Worker Example”


Vishal Vijay
March 13, 2013 Reply

Thanks

Tony
April 17, 2013 Reply

Thank you. This is a great article… well written and illustrated.

aman
May 19, 2013 Reply

This article was very well written. Thank you for taking the time to teach me about SwingWorker :)

k-rosty
June 4, 2013 Reply

i really like this article. thanks

Kasia
July 15, 2013 Reply

Very nice article. It has really helped me a lot. Thank you!

Albert Attard Albert Attard
July 22, 2013 Reply

We’re glad you liked it. You can download the full project will all examples from the SVN Repository (https://java-creed-examples.googlecode.com/svn)

oceans11
August 4, 2013 Reply

Thanks a lot bro, for this awesome tutorial

SailDolphin
August 19, 2013 Reply

Thanks! Well written.

Radek
September 12, 2013 Reply

Thanks, very nice article…

Karthik M
October 30, 2013 Reply

Really a great article. Thanks

Mario OT
November 8, 2013 Reply

Great article!
Very helpful, learned a lot! :)

mazheng
November 16, 2013 Reply

WOW, after read this article, I understand GUI. Thanks

Arundas
January 18, 2014 Reply

Good article
Thank you Albert Attard

Derek
January 21, 2014 Reply

The first part of the article gives a very clear explanation of the principles and reasoning behind Swingworker, however the code itself is puzzling as:

  1. the browse button does not work
  2. the cancel button does not work
  3. The number of times the word is found is not displayed
Albert Attard Albert Attard
January 21, 2014 Reply

Thank you for your feedback. Will have a look at it and will update were necessary.

drg
February 21, 2014 Reply

Thanks for looking at code and please let us know when fixed.

Albert Attard Albert Attard
February 22, 2014 Reply

The browse method was missing. I’ve also updated the code to be more in line with the rest of the examples. Please note that the SVN path for this project has moved to: https://java-creed-examples.googlecode.com/svn/swing/Swing Worker Example/. Let me know if this does not work.

Richard Kiefer
March 9, 2014 Reply

Amazing article! Thanks very much.

Pedro Gonzalez
June 20, 2014 Reply

Perfect article. Thank you so much.

Stefan Blaginov
July 12, 2014 Reply

Thank you for providing us with such a helpful and well written article. It was an absolute pleasure to learn.

Leave a Comment



seven − = 2