Worker Threads in Graphical Applications

A task that may run for a “long” time, even a few seconds, should normally be run on a separate thread to avoid freezing the UI.

Example: A Counter Without threads

This example shows what happens if you start a long-running task on the UI thread.

  1. The CountUp class is inside the TimerController. When the user presses Start, the controller calls CountUp.run().
  2. The run() method increments the counter in a loop and sleeps for 1 millisec each time.
  3. The run() method also updates the UI by calling displayCount(count) and updateProgress() to update the progress bar.

Clone this code: https://github.com/jbrucker/worker-threads

The program counts from 0 to some limit, in milliseconds.

Countup Iimer

Exercise: Run the application. Type a number (like 2000) and press “Start”. Does the UI behave well? Can you stop the counter?

You can see the count printed on console, but it is not updated in the window until the counter loop finishes.

Examine the Code: There is an inner class named CountUp that does the work.

run() counts from 0 to totalCount. It updates the UI and the ProgressBar.

stop() stops the run() method by setting cancelled=true.

Components in the UI are:

field name Usage
displayField Label at top displays count
inputField TextField for inputting a number
startButton Button with label “start”
stopButton Button with label “stop”
progressBar ProgressBar at bottom

Worker Threads and Services

To avoid an unresponsive UI, you should run long-running tasks in separate threads. These threads are called worker threads and appear to run in the background.

Example of worker threads:

  • An app that downloads files. Each download is one or more worker threads.
  • A notification service. It uses a worker thread to periodically check for notifications. The worker notifies the UI thread when a notification is received.

There are some special problems with using worker threads:

  • How can you monitor the worker’s progress?
  • How do you get and display the result?
  • How can the worker notify the UI thread of events?
  • How can the application cancel the worker thread?

GUI frameworks provide classes for worker threads. These classes manage communication between background threads and the UI or “Application” thread.

  • Android has AsyncTask class
  • Swing has SwingWorker class
  • JavaFX has a Worker interface with Task and Service classes

Using a JavaFX Task

The JavaFX Task class is for a one-time job that you want to run in a worker thread. The Task class handles communication between the JavaFX Application Thread (the UI thread) and the worker thread.

The Worker interface and Task class have many methods, but they are easy to understand if you group them by what they do.

The main methods of Task are:

Task<V> Meaning
message: String
progress: double
running: boolean
state: Worker.State
totalWork: double
value: V
Properties that are observable. When their value changes the observers are notified. For each property xxx there is a method named xxxProperty() to get a reference to it, for example, task.valueProperty() Don’t confuse the methods that return the property (valueProperty()) with the method that just returns the current value of the property, like getValue().
V call()
Required Method: Your Task subclass must override this method.
In the call() method your task does the actual work. While doing work your task can update value and progress so the UI is notified.
cancel()
A control method to cancel the task.
updateMessage(String message)
updateProgress(workdone, totalwork)
updateValue(V value)
Services: Call these methods from within your task’s call() method to update the message, progress, and value properties. Listeners will be notified on the UI thread whenever a value changes. You can also directly bind a UI control to a property to automatically update the control, as shown in the code below.
getMessage()
getProgress()
getState()
getValue()
isCancelled()
isRunning()
Query Methods:
Get the value of a property or test if the task is running or cancelled. getValue() returns the current “value” the the result. If the task is still running, the “value” has the value set by updateValue(). If the task is finished, this is the value returned by call().
messageProperty()
progressProperty()
valueProperty()
Get a reference to a property. Use these methods to either add a Listener or to bind the property to another property.

The way to use worker threads is to define a subclass of Task and implement the call() method. The call() method can notify the UI of its progress by calling the Service methods updateValue(), updateProgress(), and updateMessage().

Do It

Write a CountUpTask class to count from 0 to a limit using a worker thread. This can be an inner class, or top-level class (separate .java file). The value returned by the call() method will be the total count, which is an int value. So the type parameter is Integer.

/** A worker that counts slowly from 0 to a total count. */
public class CountUpTask extends Task<Integer> {
    private int totalcount;
    private int count;

    public CountUpTask(int maxcount) {
        this.totalcount = maxcount;
        this.count = 0;
    }

    @Override
    public Integer call() {
        updateMessage("Starting");
        updateProgress(0, totalcount);
       
        while(count < totalcount) {
            count += 1;
            System.out.println(count); // for testing
            // Notification services from the superclass
            updateValue(count);  
            updateMessage(Integer.toString(count));
            updateProgress(count, totalcount);
            // wait for 1 millisecond (but not very accurate)
            try { Thread.sleep(1); }
            catch (InterruptedException ex) { break; }
        }
        updateProgress(count,totalcount);
        // Return the result of the task
        return count;
    }  
}

The work is done in the call() method. This method periodically notifies the UI thread of its progress by calling updateValue(count) ,updateProgress, and updateMessage.

Running a Worker Thread

In JavaFX, you create a Thread containing your backgound Task and start it using Thread.start(), or a Java Executor or ExecutorService. In the controller class add this code:

public class TimerController {

    private CountUpTask worker;

    /** Call this method to start the task. */
    public void startWorker(ActionEvent event) {
        int count = Integer.parseInt(inputField.getText());
        worker = new CountUpTask(count);
        // automatically update the progressBar using worker's progress Property
        progressBar.progressProperty().bind( worker.progressProperty() );
        // update the displayField whenever the value of worker changes:
        ChangeListener<Integer> listener = new ChangeListener<Integer>() {
            @Override
            public void changed(ObservableValue<? extends Integer> observable,
                    Integer oldValue,
                    Integer newValue) {
                displayField.setText(newValue.toString());
            }
        };
        // add the observer (ChangeListener)
        worker.valueProperty().addListener( listener );
        new Thread(worker).start();
    }
}

In this method we bind the CountUpTask’s progress property to the progress property of a ProgressBar control. When the worker task calls updateProgress(), it will cause the ProgressBar to be updated.

To show the count in a TextField we add a ChangeListener as observer of the value property. Whenever the CountUpTask called updateValue() it will notify the ChangeListener (on the UI thread), and give it both the old and new values as parameters.

We also need a way to stop the task. Define a stopWorker method to cancel the CountUpTask:

    /** Call this method when Stop button is pressed. */
    public void stopWorker(ActionEvent event) {
        worker.cancel();
    }

Finally, add startWorker and stopWorker as Event Handler methods for the start and stop buttons in the UI. In the initialize() method change the code to:

    @FXML
    public void initialize() {
        startButton.setOnAction( this::startWorker );
        stopButton.setOnAction( this::stopWorker );
    }

Run the application. Is it more responsive?

Properties

JavaFX uses observable properties for values. The Worker Task class progress attribute is an observable Integer Property, and the value is also an observable Property. JavaFX gives you 2 ways to access these fields:

Method What is does
double getProgress() get the current value of progress
progressProperty() get observable Property for progress
Integer getValue() get current value of worker
valueProperty() get observable Property for value

To update the displayField whenever the worker updates the value property, we add an “observer” which in this case is a ChangeListener:

ChangeListener<Integer> listener = new ChangeListener<Integer>() {
    @Override
    public void changed(ObservableValue<? extends Integer> observable,
            Integer oldValue, Integer newValue) {
        displayField.setText( newValue.toString() );
    }
};
// add observer to the value Property
worker.valueProperty().addListener( listener );

Many JavaFX controls (and other classes) let you directly “bind” one of their properties to a property on another object. Some bindings are one-way and some are bidirectional (either side of the “binding” can update the other). So, we “bind” the value of the ProgressBar (a double value) directly to the Worker’s progress property (also a double):

progressBar.progressProperty().bind( worker.progressProperty() );

This is like adding a ChangeListener to update the progressbar, but a lot less code.

Instead of writing a ChangeListener for the value property, we could bind the displayField String text property directly to the Worker’s message property (also a String):

displayField.textProperty().bind( worker.messageProperty() );

Note that this means the Controller assumes that the Worker always updates the message property with the current value of the counter. An additional bit of coupling between the two objects.

Using Hook Methods for Extra Functionality

A hook method is an optional method you can override to add additional functionality to existing code. They are often used in frameworks.

A Worker task is always in one of these states:

  • State.READY - ready to be executed, but not yet run
  • State.SCHEDULED - scheduled for execution but not yet running
  • State.RUNNING - worker is running
  • State.CANCELLED - worker was cancelled
  • State.SUCCEEDED - call() method completed successfully, result is ready to be read from the value property
  • State.FAILED - worker failed, e.g. unexpected problem occured

The Task class has “hook” methods that are called when the worker enters each of these states. When the Worker enters the RUN state, the running() is called. You can override any of these methods to perform extra work when a state is entered.

For example:

@Override
protected void running() {
    System.out.println("Worker is starting!");
    // This method is called on the application thread,
    // so it is safe to update UI components.
}

You can also add an EventHandler to be notified when the worker task enters a state. To be notified when the task finishes successfully, write:

worker.setOnSucceeded( new EventHandler<WorkerStateEvent>() {
         public void handle(WorkerStateEvent event) {
             // do something when worker finishes
         }
     } );

Reference

  • JavaFX Tutorial has a section on Task and Service.