Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
162 views
in Technique[技术] by (71.8m points)

java - JavaFX 2: background and Platform.runLater vs Task/Service

I'm pretty confused about the concept of Task/Service in JavaFX.

I have used a model based on a background thread for my background work, which call Platform.runLater for any update to the UI.

Let's say I'm not interested in a progress bar or such. I'm doing some real work on my model that must be updated in the view of the GUI (e.g a list of participants which updates over time based on some connection in the background, list of participant based on some user input, classified by age and origin). This is what I usually achieve with background threads that I start, and within which I use Platform.runLater.

Now in JavaFX 2 they have all this concurrency using Tasks and Services, suggesting that it is better to use them. But I don't see any examples that achieve what I'm talking about.

Updating the progress bar by binding some properties is nice (but those are information on the task not your model).

So, how can I actually update the content of my views based on my model? Should I call Platform.runLater from within the Task? If not, what is the mechanism? How do I catch when the tasks have succeed and get the result (the update of the actual model) to update the view?

The tutorials by Oracle unfortunately were not very good in this regard. Pointing me to some good tutorials would also help.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The Task and Service classes are designed to encourage good practice and proper use of concurrency for some (but not all) common scenarios in GUI programming.

A typical scenario is that the application needs to execute some logic in response to a user action which may take a long time (maybe a long calculation, or, more commonly, a database lookup). The process will return a result which is then used to update the UI. As you know, the long-running process needs to be executed on a background thread to keep the UI responsive, and the update to the UI must be executed on the FX Application Thread.

The Task class provides an abstraction for this kind of functionality, and represents a "one-off" task that is executed and produces a result. The call() method will be executed on the background thread, and is designed to return the result of the process, and there are event listeners for when the task completes that are notified on the FX Application thread. The developer is strongly encouraged to initialize the Task implementation with immutable state and have the call() method return an immutable object, which guarantees proper synchronization between the background thread and the FX Application Thread.

There are additional common requirements on these kinds of tasks, such as updating a message or the progress as the task progresses. The application may also need to monitor the life-cycle state of the class (waiting to run, running, completed, failed with an exception, etc). Programming this correctly is quite subtly difficult, as it necessarily involves accessing mutable state in two different threads, and there are many application developers who are unaware of the subtleties. The Task class provides simple hooks for this kind of functionality and takes care of all the synchronization.

To use this functionality, just create a Task whose call() method returns the result of your computation, register a handler for when the state transitions from RUNNING to SUCCEEDED, and run the task in a background thread:

final Task<MyDataType> task = new Task<MyDataType>() {
    @Override
    public MyDataType call() throws Exception {
        // do work here...
        return result ;
    }
};

task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent event) {
        MyDataType result = task.getValue(); // result of computation
        // update UI with result
    }
});

Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();

The way this works behind the scenes is that the Task maintains a state property, which is implemented using a regular JavaFX ObjectProperty. The Task itself is wrapped in a private implementation of Callable, and the Callable implementation is the object passed to the superclass constructor. Consequently, the Callable's call() method is actually the method executed in the background thread. The Callable's call() method is implemented as follows:

  1. Schedule a call on the FX Application thread (i.e. using Platform.runLater()) that updates the state, first to SCHEDULED, then to RUNNING
  2. Invoke the call() method of the Task (i.e. the user-developed call() method)
  3. Schedule a call on the FX Application Thread that updates the value property to the result of the call() method
  4. Schedule a call on the FX Application Thread that updates the state property to SUCCEEDED

This last step will of course invoke listeners registered with the state property, and since the state change was invoked on the FX Application Thread, so to will those listeners' handle() methods.

For a full understanding of how this works, see the source code.

Commonly, the application may want to execute these tasks multiple discrete times, and monitor the current state representing all of the processes (i.e. "running" now means one instance is running, etc). The Service class simply provides a wrapper for this via a createTask() method. When the Service is started, it gets a Task instance by calling createTask(), executes it via its Executor, and transitions its own state accordingly.

There are of course many concurrency use cases that don't fit (at least cleanly) into the Task or Service implementations. If you have a single background Thread that is running for the entire duration of your application (so it represents a continuous process, rather than a one-off task), then the Task class is not a good fit. Examples of this might include a game loop, or (perhaps) polling. In these cases you may well be better off using your own Thread with Platform.runLater() to update the UI, but of course you have to handle proper synchronization of any variables that may be accessed by both threads. In my experience, it is worth spending some time thinking about whether these requirements can be re-organized into something that does fit into the Task or Service model, as if this can be done the resulting code structure is often much cleaner and easier to manage. There are certainly cases where this is not the case, however, in which case using a Thread and Platform.runLater() is appropriate.

One last comment on polling (or any other requirement for a periodically-scheduled background task). The Service class looks like a good candidate for this, but it turns out to be quite hard to manage the periodicity effectively. JavaFX 8 introduced a ScheduledService class which takes care of this functionality quite nicely, and also adds handling for cases such as repeated failure of the background task.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...