This month, too, we bring to your attention some technical challenges of the JavaFX world. In the second article we will discuss about concurrency and data binding.
The javafx.concurrent
package manages the multithread code of the
interaction with the UI and ensures that this interaction takes place in
the correct thread. The package consists of the Worker
interface and two
basic classes, Task
and Service
, both of them implementing the Worker
interface.
The Worker
interface provides the API used by a background worker which
communicates with the UI. The Task
class is a fully observable
implementation of the java.util.concurrent
. FutureTask
class and it
allows developers to implement asynchronous tasks in the JavaFX
applications. The Service
class performs these tasks.
A Worker
is, thus, an object working in a background thread. The state
of the Worker
object is observable and usable from the threads of the
JavaFX application.
The lifecycle of a Worker is defined like this: when the Worker object
is created, it is in the READY
state. After it has been programmed for
work, the Worker
object makes the transition towards the SCHEDULED
state. Afterwards, when the Worker
object is running, its state becomes
RUNNING
.
Note: Even if the Worker object started immediately, without being
programmed, it first passes through the SCHEDULED
stage and then goes to
RUNNING
.
The state of the Worker object, when it has successfully performed
becomes SUCCEEDED
, and the value property will be set according to the
result of the Worker
object. Thus, if exceptions are thrown during the
runtime of the Worker
object, its state becomes FAILED
, and the
exception property is set to the type of exception that has occurred.
The Worker
object can be interrupted in any state, by using the cancel
() method, which sends the object into the CANCELLED
state.
The progress registered on the running of the Worker
object may be
obtained through three different properties: totalWork, workDone and
progress.
The Task
class can be started in one of the following ways (the first
two would be preferable):
ExecutorService.submit (task)
;task.run ()
;The Tasks
are used to implement the work logic in a background thread.
Firstly, we have to extend the Task
class, which will overwrite the
method as call()
. The Task
class inherits the
java.utils.concurrent.FutureTask
class, which implements the Runnable
interface. That is why the Task
object can be used with the Executor API
and can be sent to a thread as parameter.
We can call the Task
object right from the FutureTask.run (), which
allows us to call this task from another thread.
We will create a CounterTask class, which extends the Task class.
public class CounterTask extends Task {
@Override
public Void call() {
final int max = 10000000;
updateProgress(0, max);
for (int i = 1; i <= max; i++) {
updateProgress(i, max);
}
return null;
}
}
The call()
method is invoked by the background thread; therefore, this
method can manipulate states that are sure to be read or written in a
background thread. For instance, manipulation the active graphical stage
from the call()
method will throw a runtime exception.
On the other hand, the Task
class is meant to be used with JavaFX
applications and it assures us that any modifications of the public
properties, error notifications, event and stage manipulators appear in
the JavaFX application thread. Within the call()
method, we may use the
methods: updateProgress()
, updateMessage()
and updateTitle()
in order
to update the values corresponding to the properties on the JavaFX
thread.
In the application, we created an instance of the previous class, called countTask and we executed it through an `ExecutorService (ExecutorService es = Executors.newSingleThreadExecutor (); ):`
@Override
public void handle(ActionEvent event) {
System.out.println("Count Started");
bar.progressProperty().bind(countTask.progressProperty());
es.execute(countTask);
}
The Service
class is designed to execute a Task
object in one or several
threads. The methods and states of the Service class must be accessed
from the JavaFX application thread. This class helps developers to
implement a correct interaction between the background threads and the
JavaFX application thread. We can start, stop, cancel and restart a
Service. A Service may run a task more than once. Thus, a service may be
declared and restarted upon request.
A Service can be run in one of the following ways:
ThreadPoolExecutor
Here we have an illustration of creating a custom service in the example below:
public class CounterService extends Service {
@Override
protected Task createTask() {
CounterTask ct = new CounterTask();
return ct;
}
}
Data binging has the purpose of simplifying the task by synchronizing the view with the data of the model. The binding observes its lists of functions in order to detect changes and it updates if they have appeared. The binding API provides a simple way to create bindings for the most common situations.
Binding is, thus, a powerful mechanism for expressing direct relations between variables. When the objects take part in bindings, the changes brought to one of them will automatically reflect upon the other. For instance, binding may be used in GUI for the automatic keeping of the read-outs synchronized with the data they refer.
The bindings are assembled from one or several sources called dependences.
In our previous example, we have used the bind () function to connect the progress bar to the counter Task. Here is the complete code of the JavaFX application:
public class CounterBarAppService extends Application {
StackPane root = new StackPane();
VBox mainBox = new VBox();
ProgressBar bar = new ProgressBar(0.0);
CounterService cs = new CounterService();
@Override
public void init() throws Exception {
super.init();
mainBox.setAlignment(Pos.CENTER);
mainBox.setSpacing(10);
Button btn = new Button();
btn.setText("Count to Ten Million!");
btn.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent event) {
System.out.println("Count Started");
bar.progressProperty().bind(cs.progressProperty());
if (cs.getState() == State.READY) {
cs.start();
}
}
});
Button restartBtn = new Button();
restartBtn.setText("Restart");
restartBtn.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent event) {
System.out.println("Count Started");
bar.progressProperty().bind(cs.progressProperty());
cs.restart();
}
});
mainBox.getChildren().add(btn);
mainBox.getChildren().add(restartBtn);
mainBox.getChildren().add(bar);
root.getChildren().add(mainBox);
}
@Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JavaFX Service Example");
primaryStage.setScene(new Scene(root, 400, 250));
primaryStage.show();
}
}
We are looking forward to discussing the new JavaFX world with you.
Enjoy your reading!