javafx.concurrent.Task Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.concurrent;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;
import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_CANCELLED;
import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_FAILED;
import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_RUNNING;
import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SCHEDULED;
import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SUCCEEDED;
/**
*
* A fully observable implementation of a {@link FutureTask}. {@code Task} exposes
* additional state and observable properties useful for programming asynchronous
* tasks in JavaFX, as defined in the {@link Worker} interface. An implementation
* of Task must override the {@link javafx.concurrent.Task#call()} method. This method
* is invoked on the background thread. Any state which is used in this method
* must be safe to read and write from a background thread. For example, manipulating
* a live scene graph from this method is unsafe and will result in runtime
* exceptions.
*
*
* Tasks are flexible and extremely useful for the encapsulation of "work". Because
* {@link Service} is designed to execute a Task, any Tasks defined by the application
* or library code can easily be used with a Service. Likewise, since Task extends
* from FutureTask, it is very easy and natural to use a Task with the java concurrency
* {@link java.util.concurrent.Executor} API. Since a Task is Runnable, you
* can also call it directly (by invoking the {@link javafx.concurrent.Task#run()} method)
* from another background thread. This allows for composition of work, or pass it to
* a new Thread constructed and executed manually. Finally, since you can
* manually create a new Thread, passing it a Runnable, it is possible to use
* the following idiom:
*
* {@code
* Thread th = new Thread(task);
* th.setDaemon(true);
* th.start();
* }
*
* Note that this code sets the daemon flag of the Thread to true. If you
* want a background thread to prevent the VM from exiting after the last
* stage is closed, then you would want daemon to be false. However, if
* you want the background threads to simply terminate after all the
* stages are closed, then you must set daemon to true.
*
*
* Although {@link java.util.concurrent.ExecutorService} defines several methods which
* take a Runnable, you should generally limit yourself to using the execute
* method inherited from {@link java.util.concurrent.Executor}.
*
*
* As with FutureTask, a Task is a one-shot class and cannot be reused. See {@link Service}
* for a reusable {@link Worker}.
*
*
* Because the Task is designed for use with JavaFX GUI applications, it ensures
* that every change to its public properties, as well as change notifications
* for state, errors, and for event handlers, all occur on the main JavaFX application
* thread. Accessing these properties from a background thread (including the
* {@link #call()} method) will result in runtime exceptions being raised. The only exception
* to this, is when initially configuring a Task, which may safely be done
* from any thread. However, once the Task has been initialized and
* started, it may only thereafter be used from the FX thread (except for those methods clearly
* marked as being appropriate for the subclass to invoke from the background thread).
*
*
* It is strongly encouraged that all Tasks be initialized with
* immutable state upon which the Task will operate. This should be done by providing
* a Task constructor which takes the parameters necessary for execution of the Task.
* Immutable state makes it easy and safe to use from any thread and ensures
* correctness in the presence of multiple threads.
*
*
* In Java there is no reliable way to "kill" a thread in process. However,
* when cancel
is called on a Task, it is important that
* the Task stop processing. A "run-away" Task might continue processing
* and updating the message, text, and progress properties even after the
* Task has been cancelled! In Java, cancelling a Task is a cooperative
* endeavor. The user of the Task will request that it be cancelled, and
* the author of the Task must check whether is has been cancelled within
* the body of the call
method. There are two ways this can
* be done. First, the Task author may check the isCancelled method,
* inherited from FutureTask
, to see whether the Task has
* been cancelled. Second, if the Task implementation makes use of any
* blocking calls (such as NIO InterruptibleChannels or Thread.sleep) and
* the task is cancelled while in such a blocking call, an
* InterruptedException is thrown. Task implementations which have blocking
* calls should recognize that an interrupted thread may be the signal for
* a cancelled task and should double check the isCancelled method to ensure
* that the InterruptedException was thrown due to the cancellation of the
* Task.
*
* Examples
*
* The following set of examples demonstrate some of the most common uses of
* Tasks.
*
*
* A Simple Loop
*
*
* The first example is a simple loop that does nothing particularly useful,
* but demonstrates the fundamental aspects of writing a Task correctly. This
* example will simply loop and print to standard out on each loop iteration.
* When it completes, it returns the number of times it iterated.
*
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Integer call() throws Exception {
* int iterations;
* {@literal for (iterations = 0; iterations < 100000; iterations++)} {
* if (isCancelled()) {
* break;
* }
* System.out.println("Iteration " + iterations);
* }
* return iterations;
* }
* };
*
*
*
* First, we define what type of value is returned from this Task. In this
* case, we want to return the number of times we iterated, so we will
* specify the Task to be of type Integer by using generics. Then, within
* the implementation of the call
method, we iterate from
* 0 to 100000. On each iteration, we check to see whether this Task has
* been cancelled. If it has been, then we break out of the loop and return
* the number of times we iterated. Otherwise a message is printed to
* the console and the iteration count increased and we continue looping.
*
*
*
* Checking for isCancelled() in the loop body is critical, otherwise the
* developer may cancel the task, but the task will continue running
* and updating both the progress and returning the incorrect result
* from the end of the call
method. A correct implementation
* of a Task will always check for cancellation.
*
*
* A Simple Loop With Progress Notification
*
*
* Similar to the previous example, except this time we will modify the
* progress of the Task in each iteration. Note that we have a choice
* to make in the case of cancellation. Do we want to set the progress back
* to -1 (indeterminate) when the Task is cancelled, or do we want to leave
* the progress where it was at? In this case, let's leave the progress alone
* and only update the message on cancellation, though updating the
* progress after cancellation is a perfectly valid choice.
*
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Integer call() throws Exception {
* int iterations;
* {@literal for (iterations = 0; iterations < 10000000; iterations++)} {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, 10000000);
* }
* return iterations;
* }
* };
*
*
*
* As before, within the for loop we check whether the Task has been
* cancelled. If it has been cancelled, we will update the Task's
* message to indicate that it has been cancelled, and then break as
* before. If the Task has not been cancelled, then we will update its
* message to indicate the current iteration and then update the
* progress to indicate the current progress.
*
*
* A Simple Loop With Progress Notification And Blocking Calls
*
*
* This example adds to the previous examples a blocking call. Because a
* blocking call may thrown an InterruptedException, and because an
* InterruptedException may occur as a result of the Task being cancelled,
* we need to be sure to handle the InterruptedException and check on the
* cancel state.
*
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Integer call() throws Exception {
* int iterations;
* {@literal for (iterations = 0; iterations < 1000; iterations++)} {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, 1000);
*
* // Now block the thread for a short time, but be sure
* // to check the interrupted exception for cancellation!
* try {
* Thread.sleep(100);
* } catch (InterruptedException interrupted) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* }
* }
* return iterations;
* }
* };
*
*
*
* Here we have added to the body of the loop a Thread.sleep
* call. Since this is a blocking call, I have to handle the potential
* InterruptedException. Within the catch block, I will check whether
* the Task has been cancelled, and if so, update the message accordingly
* and break out of the loop.
*
*
* A Task Which Takes Parameters
*
*
* Most Tasks require some parameters in order to do useful work. For
* example, a DeleteRecordTask needs the object or primary key to delete
* from the database. A ReadFileTask needs the URI of the file to be read.
* Because Tasks operate on a background thread, care must be taken to
* make sure the body of the call
method does not read or
* modify any shared state. There are two techniques most useful for
* doing this: using final variables, and passing variables to a Task
* during construction.
*
*
*
* When using a Task as an anonymous class, the most natural way to pass
* parameters to the Task is by using final variables. In this example,
* we pass to the Task the total number of times the Task should iterate.
*
*
*
* final int totalIterations = 9000000;
* {@literal Task task = new Task()} {
* {@literal @Override} protected Integer call() throws Exception {
* int iterations;
* {@literal for (iterations = 0; iterations < totalIterations; iterations++)} {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, totalIterations);
* }
* return iterations;
* }
* };
*
*
*
* Since totalIterations
is final, the call
* method can safely read it and refer to it from a background thread.
*
*
*
* When writing Task libraries (as opposed to specific-use implementations),
* we need to use a different technique. In this case, I will create an
* IteratingTask which performs the same work as above. This time, since
* the IteratingTask is defined in its own file, it will need to have
* parameters passed to it in its constructor. These parameters are
* assigned to final variables.
*
*
*
* {@literal public class IteratingTask extends Task} {
* private final int totalIterations;
*
* public IteratingTask(int totalIterations) {
* this.totalIterations = totalIterations;
* }
*
* {@literal @Override} protected Integer call() throws Exception {
* int iterations = 0;
* {@literal for (iterations = 0; iterations < totalIterations; iterations++)} {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, totalIterations);
* }
* return iterations;
* }
* }
*
*
* And then when used:
*
* {@code
* IteratingTask task = new IteratingTask(8000000);
* }
*
* In this way, parameters are passed to the IteratingTask in a safe
* manner, and again, are final. Thus, the call
method can
* safely read this state from a background thread.
*
* WARNING: Do not pass mutable state to a Task and then operate on it
* from a background thread. Doing so may introduce race conditions. In
* particular, suppose you had a SaveCustomerTask which took a Customer
* in its constructor. Although the SaveCustomerTask may have a final
* reference to the Customer, if the Customer object is mutable, then it
* is possible that both the SaveCustomerTask and some other application code
* will be reading or modifying the state of the Customer from different
* threads. Be very careful in such cases, that while a mutable object such
* as this Customer is being used from a background thread, that it is
* not being used also from another thread. In particular, if the background
* thread is reading data from the database and updating the Customer object,
* and the Customer object is bound to scene graph nodes (such as UI
* controls), then there could be a violation of threading rules! For such
* cases, modify the Customer object from the FX Application Thread rather
* than from the background thread.
*
*
* {@literal public class UpdateCustomerTask extends Task} {
* private final Customer customer;
*
* public UpdateCustomerTask(Customer customer) {
* this.customer = customer;
* }
*
* {@literal @Override} protected Customer call() throws Exception {
* // pseudo-code:
* // query the database
* // read the values
*
* // Now update the customer
* Platform.runLater(new Runnable() {
* {@literal @Override} public void run() {
* customer.setFirstName(rs.getString("FirstName"));
* // etc
* }
* });
*
* return customer;
* }
* }
*
*
* A Task Which Returns No Value
*
*
* Many, if not most, Tasks should return a value upon completion. For
* CRUD Tasks, one would expect that a "Create" Task would return the newly
* created object or primary key, a "Read" Task would return the read
* object, an "Update" task would return the number of records updated,
* and a "Delete" task would return the number of records deleted.
*
*
*
* However sometimes there just isn't anything truly useful to return.
* For example, I might have a Task which writes to a file. Task has built
* into it a mechanism for indicating whether it has succeeded or failed
* along with the number of bytes written (the progress), and thus there is
* nothing really for me to return. In such a case, you can use the Void
* type. This is a special type in the Java language which can only be
* assigned the value of null
. You would use it as follows:
*
*
*
* final String filePath = "/foo.txt";
* final String contents = "Some contents";
* {@literal Task task = new Task()} {
* {@literal @Override} protected Void call() throws Exception {
* File file = new File(filePath);
* FileOutputStream out = new FileOutputStream(file);
* // ... and other code to write the contents ...
*
* // Return null at the end of a Task of type Void
* return null;
* }
* };
*
*
* A Task Which Returns An ObservableList
*
* Because the ListView, TableView, and other UI controls and scene graph
* nodes make use of ObservableList, it is common to want to create and return
* an ObservableList from a Task. When you do not care to display intermediate
* values, the easiest way to correctly write such a Task is simply to
* construct an ObservableList within the call
method, and then
* return it at the conclusion of the Task.
*
*
* {@literal Task> task = new Task>}() {
* {@literal @Override protected ObservableList call() throws Exception} {
* updateMessage("Creating Rectangles");
* {@literal ObservableList results = FXCollections.observableArrayList();}
* {@literal for (int i=0; i<100; i++)} {
* if (isCancelled()) break;
* Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* results.add(r);
* updateProgress(i, 100);
* }
* return results;
* }
* };
*
*
* In the above example, we are going to create 100 rectangles and return
* them from this task. An ObservableList is created within the
* call
method, populated, and then returned.
*
* A Task Which Returns Partial Results
*
* Sometimes you want to create a Task which will return partial results.
* Perhaps you are building a complex scene graph and want to show the
* scene graph as it is being constructed. Or perhaps you are reading a large
* amount of data over the network and want to display the entries in a
* TableView as the data is arriving. In such cases, there is some shared state
* available both to the FX Application Thread and the background thread.
* Great care must be taken to never update shared state from any
* thread other than the FX Application Thread.
*
* The easiest way to do this is to take advantage of the {@link #updateValue(Object)} method.
* This method may be called repeatedly from the background thread. Updates are coalesced to
* prevent saturation of the FX event queue. This means you can call it as frequently as
* you like from the background thread but only the most recent set is ultimately set.
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Long call() throws Exception {
* long a = 0;
* long b = 1;
* {@literal for (long i = 0; i < Long.MAX_VALUE; i++)} {
* updateValue(a);
* a += b;
* b = a - b;
* }
* return a;
* }
* };
*
*
* Another way to do this is to expose a new property on the Task
* which will represent the partial result. Then make sure to use
* Platform.runLater
when updating the partial result.
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Long call() throws Exception {
* long a = 0;
* long b = 1;
* {@literal for (long i = 0; i < Long.MAX_VALUE; i++)} {
* final long v = a;
* Platform.runLater(new Runnable() {
* {@literal @Override} public void run() {
* updateValue(v);
* }
* }
* a += b;
* b = a - b;
* }
* return a;
* }
* };
*
*
* Suppose instead of updating a single value, you want to populate an ObservableList
* with results as they are obtained. One approach is to expose a new property on the Task
* which will represent the partial result. Then make sure to use
* Platform.runLater
when adding new items to the partial
* result.
*
*
* {@literal public class PartialResultsTask extends Task>} {
* // Uses Java 7 diamond operator
* {@literal private ReadOnlyObjectWrapper> partialResults =}
* {@literal new ReadOnlyObjectWrapper<>(this, "partialResults",
* FXCollections.observableArrayList(new ArrayList()));}
*
* {@literal public final ObservableList getPartialResults()} { return partialResults.get(); }
* {@literal public final ReadOnlyObjectProperty> partialResultsProperty()} {
* return partialResults.getReadOnlyProperty();
* }
*
* {@literal @Override protected ObservableList call() throws Exception} {
* updateMessage("Creating Rectangles...");
* {@literal for (int i=0; i<100; i++)} {
* if (isCancelled()) break;
* final Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* Platform.runLater(new Runnable() {
* {@literal @Override} public void run() {
* partialResults.get().add(r);
* }
* });
* updateProgress(i, 100);
* }
* return partialResults.get();
* }
* }
*
*
* A Task Which Modifies The Scene Graph
*
* Generally, Tasks should not interact directly with the UI. Doing so
* creates a tight coupling between a specific Task implementation and a
* specific part of your UI. However, when you do want to create such a
* coupling, you must ensure that you use Platform.runLater
* so that any modifications of the scene graph occur on the
* FX Application Thread.
*
*
* final Group group = new Group();
* {@literal Task task = new Task()} {
* {@literal @Override} protected Void call() throws Exception {
* {@literal for (int i=0; i<100; i++)} {
* if (isCancelled()) break;
* final Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* Platform.runLater(new Runnable() {
* {@literal @Override} public void run() {
* group.getChildren().add(r);
* }
* });
* }
* return null;
* }
* };
*
*
* Reacting To State Changes Generically
*
* Sometimes you may want to write a Task which updates its progress,
* message, text, or in some other way reacts whenever a state change
* happens on the Task. For example, you may want to change the status
* message on the Task on Failure, Success, Running, or Cancelled state changes.
*
*
* {@literal Task task = new Task()} {
* {@literal @Override} protected Integer call() throws Exception {
* int iterations = 0;
* {@literal for (iterations = 0; iterations < 100000; iterations++)} {
* if (isCancelled()) {
* break;
* }
* System.out.println("Iteration " + iterations);
* }
* return iterations;
* }
*
* {@literal @Override} protected void succeeded() {
* super.succeeded();
* updateMessage("Done!");
* }
*
* {@literal @Override} protected void cancelled() {
* super.cancelled();
* updateMessage("Cancelled!");
* }
*
* {@literal @Override} protected void failed() {
* super.failed();
* updateMessage("Failed!");
* }
* };
*
* @since JavaFX 2.0
*/
public abstract class Task extends FutureTask implements Worker, EventTarget {
/**
* Used to send workDone updates in a thread-safe manner from the subclass
* to the FX application thread and workDone related properties. AtomicReference
* is used so as to coalesce updates such that we don't flood the event queue.
*/
private AtomicReference progressUpdate = new AtomicReference<>();
/**
* Used to send message updates in a thread-safe manner from the subclass
* to the FX application thread. AtomicReference is used so as to coalesce
* updates such that we don't flood the event queue.
*/
private AtomicReference messageUpdate = new AtomicReference<>();
/**
* Used to send title updates in a thread-safe manner from the subclass
* to the FX application thread. AtomicReference is used so as to coalesce
* updates such that we don't flood the event queue.
*/
private AtomicReference titleUpdate = new AtomicReference<>();
/**
* Used to send value updates in a thread-safe manner from the subclass
* to the FX application thread. AtomicReference is used so as to coalesce
* updates such that we don't flood the event queue.
*/
private AtomicReference valueUpdate = new AtomicReference<>();
/**
* This is used so we have a thread-safe way to ask whether the task was
* started in the checkThread() method.
*/
private volatile boolean started = false;
/**
* Creates a new Task.
*/
public Task() {
this(new TaskCallable());
}
/**
* This bit of construction trickery is necessary because otherwise there is
* no way for the main constructor to both create the callable and maintain
* a reference to it, which is necessary because an anonymous callable construction
* cannot reference the implicit "this". We leverage an internal Callable
* so that all the pre-built semantics around cancel and so forth are
* handled correctly.
*
* @param callableAdapter non-null implementation of the
* TaskCallable adapter
*/
private Task(final TaskCallable callableAdapter) {
super(callableAdapter);
callableAdapter.task = this;
}
/**
* Invoked when the Task is executed, the call method must be overridden and
* implemented by subclasses. The call method actually performs the
* background thread logic. Only the updateProgress, updateMessage, updateValue and
* updateTitle methods of Task may be called from code within this method.
* Any other interaction with the Task from the background thread will result
* in runtime exceptions.
*
* @return The result of the background work, if any.
* @throws Exception an unhandled exception which occurred during the
* background operation
*/
protected abstract V call() throws Exception;
private ObjectProperty state = new SimpleObjectProperty<>(this, "state", Worker.State.READY);
final void setState(Worker.State value) { // package access for the Service
checkThread();
final Worker.State s = getState();
if (s != Worker.State.CANCELLED) {
this.state.set(value);
// Make sure the running flag is set
setRunning(value == Worker.State.SCHEDULED || value == Worker.State.RUNNING);
// Invoke the event handlers, and then call the protected methods.
switch (state.get()) {
case CANCELLED:
fireEvent(new WorkerStateEvent(this, WORKER_STATE_CANCELLED));
cancelled();
break;
case FAILED:
fireEvent(new WorkerStateEvent(this, WORKER_STATE_FAILED));
failed();
break;
case READY:
// This even can never meaningfully occur, because the
// Task begins life as ready and can never go back to it!
break;
case RUNNING:
fireEvent(new WorkerStateEvent(this, WORKER_STATE_RUNNING));
running();
break;
case SCHEDULED:
fireEvent(new WorkerStateEvent(this, WORKER_STATE_SCHEDULED));
scheduled();
break;
case SUCCEEDED:
fireEvent(new WorkerStateEvent(this, WORKER_STATE_SUCCEEDED));
succeeded();
break;
default: throw new AssertionError("Should be unreachable");
}
}
}
@Override public final Worker.State getState() { checkThread(); return state.get(); }
@Override public final ReadOnlyObjectProperty stateProperty() { checkThread(); return state; }
/**
* The onSchedule event handler is called whenever the Task state
* transitions to the SCHEDULED state.
*
* @return the onScheduled event handler property
* @since JavaFX 2.1
*/
public final ObjectProperty> onScheduledProperty() {
checkThread();
return getEventHelper().onScheduledProperty();
}
/**
* The onSchedule event handler is called whenever the Task state
* transitions to the SCHEDULED state.
*
* @return the onScheduled event handler, if any
* @since JavaFX 2.1
*/
public final EventHandler getOnScheduled() {
checkThread();
return eventHelper == null ? null : eventHelper.getOnScheduled();
}
/**
* The onSchedule event handler is called whenever the Task state
* transitions to the SCHEDULED state.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 2.1
*/
public final void setOnScheduled(EventHandler value) {
checkThread();
getEventHelper().setOnScheduled(value);
}
/**
* A protected convenience method for subclasses, called whenever the
* state of the Task has transitioned to the SCHEDULED state.
* This method is invoked on the FX Application Thread after the Task has been fully transitioned to
* the new state.
* @since JavaFX 2.1
*/
protected void scheduled() { }
/**
* The onRunning event handler is called whenever the Task state
* transitions to the RUNNING state.
*
* @return the onRunning event handler property
* @since JavaFX 2.1
*/
public final ObjectProperty> onRunningProperty() {
checkThread();
return getEventHelper().onRunningProperty();
}
/**
* The onRunning event handler is called whenever the Task state
* transitions to the RUNNING state.
*
* @return the onRunning event handler, if any
* @since JavaFX 2.1
*/
public final EventHandler getOnRunning() {
checkThread();
return eventHelper == null ? null : eventHelper.getOnRunning();
}
/**
* The onRunning event handler is called whenever the Task state
* transitions to the RUNNING state.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 2.1
*/
public final void setOnRunning(EventHandler value) {
checkThread();
getEventHelper().setOnRunning(value);
}
/**
* A protected convenience method for subclasses, called whenever the
* state of the Task has transitioned to the RUNNING state.
* This method is invoked on the FX Application Thread after the Task has been fully transitioned to
* the new state.
* @since JavaFX 2.1
*/
protected void running() { }
/**
* The onSucceeded event handler is called whenever the Task state
* transitions to the SUCCEEDED state.
*
* @return the onSucceeded event handler property
* @since JavaFX 2.1
*/
public final ObjectProperty> onSucceededProperty() {
checkThread();
return getEventHelper().onSucceededProperty();
}
/**
* The onSucceeded event handler is called whenever the Task state
* transitions to the SUCCEEDED state.
*
* @return the onSucceeded event handler, if any
* @since JavaFX 2.1
*/
public final EventHandler getOnSucceeded() {
checkThread();
return eventHelper == null ? null : eventHelper.getOnSucceeded();
}
/**
* The onSucceeded event handler is called whenever the Task state
* transitions to the SUCCEEDED state.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 2.1
*/
public final void setOnSucceeded(EventHandler value) {
checkThread();
getEventHelper().setOnSucceeded(value);
}
/**
* A protected convenience method for subclasses, called whenever the
* state of the Task has transitioned to the SUCCEEDED state.
* This method is invoked on the FX Application Thread after the Task has been fully transitioned to
* the new state.
* @since JavaFX 2.1
*/
protected void succeeded() { }
/**
* The onCancelled event handler is called whenever the Task state
* transitions to the CANCELLED state.
*
* @return the onCancelled event handler property
* @since JavaFX 2.1
*/
public final ObjectProperty> onCancelledProperty() {
checkThread();
return getEventHelper().onCancelledProperty();
}
/**
* The onCancelled event handler is called whenever the Task state
* transitions to the CANCELLED state.
*
* @return the onCancelled event handler, if any
* @since JavaFX 2.1
*/
public final EventHandler getOnCancelled() {
checkThread();
return eventHelper == null ? null : eventHelper.getOnCancelled();
}
/**
* The onCancelled event handler is called whenever the Task state
* transitions to the CANCELLED state.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 2.1
*/
public final void setOnCancelled(EventHandler value) {
checkThread();
getEventHelper().setOnCancelled(value);
}
/**
* A protected convenience method for subclasses, called whenever the
* state of the Task has transitioned to the CANCELLED state.
* This method is invoked on the FX Application Thread after the Task has been fully transitioned to
* the new state.
* @since JavaFX 2.1
*/
protected void cancelled() { }
/**
* The onFailed event handler is called whenever the Task state
* transitions to the FAILED state.
*
* @return the onFailed event handler property
* @since JavaFX 2.1
*/
public final ObjectProperty> onFailedProperty() {
checkThread();
return getEventHelper().onFailedProperty();
}
/**
* The onFailed event handler is called whenever the Task state
* transitions to the FAILED state.
*
* @return the onFailed event handler, if any
* @since JavaFX 2.1
*/
public final EventHandler getOnFailed() {
checkThread();
return eventHelper == null ? null : eventHelper.getOnFailed();
}
/**
* The onFailed event handler is called whenever the Task state
* transitions to the FAILED state.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 2.1
*/
public final void setOnFailed(EventHandler value) {
checkThread();
getEventHelper().setOnFailed(value);
}
/**
* A protected convenience method for subclasses, called whenever the
* state of the Task has transitioned to the FAILED state.
* This method is invoked on the FX Application Thread after the Task has been fully transitioned to
* the new state.
* @since JavaFX 2.1
*/
protected void failed() { }
private final ObjectProperty value = new SimpleObjectProperty<>(this, "value");
private void setValue(V v) { checkThread(); value.set(v); }
@Override public final V getValue() { checkThread(); return value.get(); }
@Override public final ReadOnlyObjectProperty valueProperty() { checkThread(); return value; }
private final ObjectProperty exception = new SimpleObjectProperty<>(this, "exception");
private void _setException(Throwable value) { checkThread(); exception.set(value); }
@Override public final Throwable getException() { checkThread(); return exception.get(); }
@Override public final ReadOnlyObjectProperty exceptionProperty() { checkThread(); return exception; }
private final DoubleProperty workDone = new SimpleDoubleProperty(this, "workDone", -1);
private void setWorkDone(double value) { checkThread(); workDone.set(value); }
@Override public final double getWorkDone() { checkThread(); return workDone.get(); }
@Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone; }
private final DoubleProperty totalWork = new SimpleDoubleProperty(this, "totalWork", -1);
private void setTotalWork(double value) { checkThread(); totalWork.set(value); }
@Override public final double getTotalWork() { checkThread(); return totalWork.get(); }
@Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWork; }
private final DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1);
private void setProgress(double value) { checkThread(); progress.set(value); }
@Override public final double getProgress() { checkThread(); return progress.get(); }
@Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress; }
private final BooleanProperty running = new SimpleBooleanProperty(this, "running", false);
private void setRunning(boolean value) { checkThread(); running.set(value); }
@Override public final boolean isRunning() { checkThread(); return running.get(); }
@Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; }
private final StringProperty message = new SimpleStringProperty(this, "message", "");
@Override public final String getMessage() { checkThread(); return message.get(); }
@Override public final ReadOnlyStringProperty messageProperty() { checkThread(); return message; }
private final StringProperty title = new SimpleStringProperty(this, "title", "");
@Override public final String getTitle() { checkThread(); return title.get(); }
@Override public final ReadOnlyStringProperty titleProperty() { checkThread(); return title; }
@Override public final boolean cancel() {
return cancel(true);
}
// Need to assert the modifyThread permission so an app can cancel
// a task that it created (the default executor for the service runs in
// its own thread group)
// Note that this is needed when running with a security manager.
private static final Permission modifyThreadPerm = new RuntimePermission("modifyThread");
@Override public boolean cancel(boolean mayInterruptIfRunning) {
// Delegate to the super implementation to actually attempt to cancel this thing
// Assert the modifyThread permission
@SuppressWarnings("removal")
boolean flag = AccessController.doPrivileged(
(PrivilegedAction) () -> super.cancel(mayInterruptIfRunning),
null,
modifyThreadPerm);
// If cancel succeeded (according to the semantics of the Future cancel method),
// then we need to make sure the State flag is set appropriately
if (flag) {
// If this method was called on the FX application thread, then we can
// just update the state directly and this will make sure that after
// the cancel method was called, the state will be set correctly
// (otherwise it would be indeterminate). However if the cancel method was
// called off the FX app thread, then we must use runLater, and the
// state flag will not be readable immediately after this call. However,
// that would be the case anyway since these properties are not thread-safe.
if (isFxApplicationThread()) {
setState(Worker.State.CANCELLED);
} else {
runLater(() -> setState(Worker.State.CANCELLED));
}
}
// return the flag
return flag;
}
/**
* Updates the workDone
, totalWork
,
* and progress
properties. Calls to updateProgress
* are coalesced and run later on the FX application thread, and calls
* to updateProgress, even from the FX Application thread, may not
* necessarily result in immediate updates to these properties, and
* intermediate workDone values may be coalesced to save on event
* notifications. max
becomes the new value for
* totalWork
.
*
* This method is safe to be called from any thread.
*
*
* @param workDone A value from Long.MIN_VALUE up to max. If the value is greater
* than max, then it will be clamped at max.
* If the value passed is negative then the resulting percent
* done will be -1 (thus, indeterminate).
* @param max A value from Long.MIN_VALUE to Long.MAX_VALUE.
* @see #updateProgress(double, double)
*/
protected void updateProgress(long workDone, long max) {
updateProgress((double)workDone, (double)max);
}
/**
* Updates the workDone
, totalWork
,
* and progress
properties. Calls to updateProgress
* are coalesced and run later on the FX application thread, and calls
* to updateProgress, even from the FX Application thread, may not
* necessarily result in immediate updates to these properties, and
* intermediate workDone values may be coalesced to save on event
* notifications. max
becomes the new value for
* totalWork
.
*
* This method is safe to be called from any thread.
*
*
* @param workDone A value from Double.MIN_VALUE up to max. If the value is greater
* than max, then it will be clamped at max.
* If the value passed is negative, or Infinity, or NaN,
* then the resulting percentDone will be -1 (thus, indeterminate).
* @param max A value from Double.MIN_VALUE to Double.MAX_VALUE. Infinity and NaN are treated as -1.
* @since JavaFX 2.2
*/
protected void updateProgress(double workDone, double max) {
// Adjust Infinity / NaN to be -1 for both workDone and max.
if (Double.isInfinite(workDone) || Double.isNaN(workDone)) {
workDone = -1;
}
if (Double.isInfinite(max) || Double.isNaN(max)) {
max = -1;
}
if (workDone < 0) {
workDone = -1;
}
if (max < 0) {
max = -1;
}
// Clamp the workDone if necessary so as not to exceed max
if (workDone > max) {
workDone = max;
}
if (isFxApplicationThread()) {
_updateProgress(workDone, max);
} else if (progressUpdate.getAndSet(new ProgressUpdate(workDone, max)) == null) {
runLater(() -> {
final ProgressUpdate update = progressUpdate.getAndSet(null);
_updateProgress(update.workDone, update.totalWork);
});
}
}
private void _updateProgress(double workDone, double max) {
setTotalWork(max);
setWorkDone(workDone);
if (workDone == -1) {
setProgress(-1);
} else {
setProgress(workDone / max);
}
}
/**
* Updates the message
property. Calls to updateMessage
* are coalesced and run later on the FX application thread, so calls
* to updateMessage, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate message values may be coalesced to save on event
* notifications.
*
* This method is safe to be called from any thread.
*
*
* @param message the new message
*/
protected void updateMessage(String message) {
if (isFxApplicationThread()) {
this.message.set(message);
} else {
// As with the workDone, it might be that the background thread
// will update this message quite frequently, and we need
// to throttle the updates so as not to completely clobber
// the event dispatching system.
if (messageUpdate.getAndSet(message) == null) {
runLater(new Runnable() {
@Override public void run() {
final String message = messageUpdate.getAndSet(null);
Task.this.message.set(message);
}
});
}
}
}
/**
* Updates the title
property. Calls to updateTitle
* are coalesced and run later on the FX application thread, so calls
* to updateTitle, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate title values may be coalesced to save on event
* notifications.
*
* This method is safe to be called from any thread.
*
*
* @param title the new title
*/
protected void updateTitle(String title) {
if (isFxApplicationThread()) {
this.title.set(title);
} else {
// As with the workDone, it might be that the background thread
// will update this title quite frequently, and we need
// to throttle the updates so as not to completely clobber
// the event dispatching system.
if (titleUpdate.getAndSet(title) == null) {
runLater(new Runnable() {
@Override public void run() {
final String title = titleUpdate.getAndSet(null);
Task.this.title.set(title);
}
});
}
}
}
/**
* Updates the value
property. Calls to updateValue
* are coalesced and run later on the FX application thread, so calls
* to updateValue, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate values may be coalesced to save on event
* notifications.
*
* This method is safe to be called from any thread.
*
*
* @param value the new value
* @since JavaFX 8.0
*/
protected void updateValue(V value) {
if (isFxApplicationThread()) {
this.value.set(value);
} else {
// As with the workDone, it might be that the background thread
// will update this value quite frequently, and we need
// to throttle the updates so as not to completely clobber
// the event dispatching system.
if (valueUpdate.getAndSet(value) == null) {
runLater(() -> Task.this.value.set(valueUpdate.getAndSet(null)));
}
}
}
/*
* IMPLEMENTATION
*/
private void checkThread() {
if (started && !isFxApplicationThread()) {
throw new IllegalStateException("Task must only be used from the FX Application Thread");
}
}
// This method exists for the sake of testing, so I can subclass and override
// this method in the test and not actually use Platform.runLater.
void runLater(Runnable r) {
Platform.runLater(r);
}
// This method exists for the sake of testing, so I can subclass and override
// this method in the test and not actually use Platform.isFxApplicationThread.
boolean isFxApplicationThread() {
return Platform.isFxApplicationThread();
}
/* *************************************************************************
* *
* Event Dispatch *
* *
**************************************************************************/
private EventHelper eventHelper = null;
private EventHelper getEventHelper() {
if (eventHelper == null) {
eventHelper = new EventHelper(this);
}
return eventHelper;
}
/**
* {@inheritDoc}
* @since JavaFX 2.1
*/
@Override
public final void addEventHandler(
final EventType eventType,
final EventHandler super T> eventHandler) {
checkThread();
getEventHelper().addEventHandler(eventType, eventHandler);
}
/**
* {@inheritDoc}
* @since JavaFX 2.1
*/
@Override
public final void removeEventHandler(
final EventType eventType,
final EventHandler super T> eventHandler) {
checkThread();
getEventHelper().removeEventHandler(eventType, eventHandler);
}
/**
* {@inheritDoc}
* @since JavaFX 2.1
*/
@Override
public final void addEventFilter(
final EventType eventType,
final EventHandler super T> eventFilter) {
checkThread();
getEventHelper().addEventFilter(eventType, eventFilter);
}
/**
* {@inheritDoc}
* @since JavaFX 2.1
*/
@Override
public final void removeEventFilter(
final EventType eventType,
final EventHandler super T> eventFilter) {
checkThread();
getEventHelper().removeEventFilter(eventType, eventFilter);
}
/**
* Sets the handler to use for this event type. There can only be one such
* handler specified at a time. This handler is guaranteed to be called
* first. This is used for registering the user-defined onFoo event
* handlers.
*
* @param the specific event class of the handler
* @param eventType the event type to associate with the given eventHandler
* @param eventHandler the handler to register, or null to unregister
* @throws NullPointerException if the event type is null
* @since JavaFX 2.1
*/
protected final void setEventHandler(
final EventType eventType,
final EventHandler super T> eventHandler) {
checkThread();
getEventHelper().setEventHandler(eventType, eventHandler);
}
/**
* Fires the specified event. Any event filter encountered will
* be notified and can consume the event. If not consumed by the filters,
* the event handlers on this task are notified. If these don't consume the
* event either, then all event handlers are called and can consume the
* event.
*
* This method must be called on the FX user thread.
*
* @param event the event to fire
* @since JavaFX 2.1
*/
public final void fireEvent(Event event) {
checkThread();
getEventHelper().fireEvent(event);
}
@Override
public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
checkThread();
return getEventHelper().buildEventDispatchChain(tail);
}
/**
* A struct like class that contains the last workDone update information.
* What we do when updateProgress is called, is we create a new ProgressUpdate
* object and store it. If it was null, then we fire off a new Runnable
* using RunLater, which will eventually read the latest and set it to null
* atomically. If it was not null, then we simply update it.
*/
private static final class ProgressUpdate {
private final double workDone;
private final double totalWork;
private ProgressUpdate(double p, double m) {
this.workDone = p;
this.totalWork = m;
}
}
/**
* TaskCallable actually implements the Callable contract as defined for
* the FutureTask class, and is necessary so as to allow us to intercept
* the call() operation to update state on the Task as appropriate.
* @param
*/
private static final class TaskCallable implements Callable {
/**
* The Task that is going to use this TaskCallable
*/
private Task task;
/**
* Create a TaskCallable. The concurrent and other fields MUST be set
* immediately after creation.
*/
private TaskCallable() { }
/**
* Invoked by the system when it is time to run the client code. This
* implementation is where we modify the state and other properties
* and from which we invoke the events.
*
* @return The result of the Task call method
* @throws Exception any exception which occurred
*/
@Override public V call() throws Exception {
// If the Task is sent to an ExecutorService for execution, then we
// will need to make sure that we transition first to the SCHEDULED
// state before then transitioning to the RUNNING state. If the
// Task was executed by a Service, then it will have already been
// in the SCHEDULED state and setting it again here has no negative
// effect. But we must ensure that SCHEDULED is visited before RUNNING
// in all cases so that developer code can be consistent.
task.started = true;
task.runLater(() -> {
task.setState(Worker.State.SCHEDULED);
task.setState(Worker.State.RUNNING);
});
// Go ahead and delegate to the wrapped callable
try {
final V result = task.call();
if (!task.isCancelled()) {
// If it was not cancelled, then we take the return
// value and set it as the result.
task.runLater(() -> {
// The result must be set first, so that when the
// SUCCEEDED flag is set, the value will be available
// The alternative is not the case, because you
// can assume if the result is set, it has
// succeeded.
task.updateValue(result);
task.setState(Worker.State.SUCCEEDED);
});
return result;
} else {
// Since cancelled Future/FutureTask doesn't return any value,
// the returned value is going to be trashed, so we can jus return null
return null;
}
} catch (final Throwable th) {
// Be sure to set the state after setting the cause of failure
// so that developers handling the state change events have a
// throwable to inspect when they get the FAILED state. Note
// that the other way around is not important -- when a developer
// observes the causeOfFailure is set to a non-null value, even
// though the state has not yet been updated, he can infer that
// it will be FAILED because it can be nothing other than FAILED
// in that circumstance.
task.runLater(() -> {
task._setException(th);
task.setState(Worker.State.FAILED);
});
// Some error occurred during the call (it might be
// an exception (either runtime or checked), or it might
// be an error. In any case, we capture the throwable,
// record it as the causeOfFailure, and then rethrow. However
// since the Callable interface requires that we throw an
// Exception (not Throwable), we have to wrap the exception
// if it is not already one.
if (th instanceof Exception) {
throw (Exception) th;
} else {
throw new Exception(th);
}
}
}
}
}