0

This is my JavaFX Controller

public class MainController {
   private Future<Graph> operation;
   private ExecutorService executor = Executors.newSingleThreadExecutor();

   @FXML
   private void createSession() { //invoked by a button click in the view
      //GraphCreationSession implements Callable<Graph>
      GraphCreationSession graphSession = new GraphCreationSession();

      if (operation != null && !operation.isDone()) {
         //cancel previous session
         operation.cancel(true);
      }
      operation = executor.submit(graphSession);
      ???
    }
 }

So my question is, what is the idiom to deal with the result of the Future in a javaFX context?

I know I can do operation.get() and the thread will block until the operation is finished, but I would be blocking the Application thread. I'm thinking of a callback when the Callable finishes and I found out CompletableFuture, which kind of does that via thenAccept but based on this answer the thread will still be blocked, which defeats the point of a Future, like the answer mentions.

In my particular case the result of the Callable (Graph in my sample) contains a result that I want to display in a panel when the operation completes.

Community
  • 1
  • 1
Hilikus
  • 9,954
  • 14
  • 65
  • 118
  • Use the [`Task` API](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html). That has built-in callbacks which are executed on the FX Application Thread as the execution state of the task changes. – James_D Mar 08 '15 at 17:27
  • @James_D that sounds useful, but how would I go about creating the Task in this context? Do I convert the Future to a Task somehow or do I submit the Callable to the executor in a different way that returns a task? – Hilikus Mar 08 '15 at 17:50

1 Answers1

3

The easiest way to do this is to change GraphCreationSession so it is a subclass of Task<Graph> instead of an implementation of Callable<Graph>:

public class GraphCreationSession extends Task<Graph> {

    @Override
    public Graph call() throws Exception {
        // implementation as before...
    }
}

Then you can do

public class MainController {
   private ExecutorService executor = Executors.newSingleThreadExecutor();
   private GraphCreationSession graphSession ;

   @FXML
   private void createSession() { //invoked by a button click in the view

      if (graphSession != null && !graphSession.getState()==Worker.State.RUNNING) {
         //cancel previous session
         graphSession.cancel(true);
      }
      graphSession = new GraphCreationSession();
      graphSession.setOnSucceeded(event -> {
          Graph graph = graphSession.getValue();
          // update UI...
      });
      executor.execute(graphSession);
    }
 }

If you can't change GraphCreationSession, or want it to be independent of the javafx API, then just wrap it in a trivial Task implementation:

public class MainController {

    private Task<Graph> graphSession ;
    // ...

    @FXML
    public void createSession() {

        // ...

        graphSession = new Task<Graph>() {
            @Override
            public Graph call() throws Exception {
                return new GraphCreationSession().call();
            }
        };

        graphSession.setOnSucceeded(...);
        executor.execute(graphSession);
   }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I did the wrapping method to separate javafx from the core of my app. it works nicely except for one little thing: `executor.submit(graphTask)` now returns `Future>` instead of `Future` like before. This is because `Task` implements `Runnable` instead of `Callable`. Is there any way to make it typesafe? – Hilikus Mar 08 '15 at 19:34
  • 2
    The `Task` itself is the `Future`... you don't need to retrieve it from the executor. You probably don't need it anyway as you can just call `graphSession.getValue()` inside the `onSucceeded` handler. – James_D Mar 08 '15 at 19:37
  • ah, nevermind. I don't need the Future anymore since I have the Task – Hilikus Mar 08 '15 at 19:40