1

I have a javafx app, and I want to surround some code with "waiting" feature. So my code can be Runnable and Callable. The problem is getting result from Callabe. I tried to play with:

  • wait()/notify()
  • Platform.runLater
  • creating daemon threads by hands
  • Service

after reading some articles here, but it doesn't help.

How I want to call it:

            final String a =
                    CommonHelper.showWaiting(() -> {
                             System.out.println("test");
                             return "test2";  
                    });

That's how I work with Runnable:

public static void showWaiting(Runnable runnable) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        try {
            executorService.submit(new WaitingTask<>(executorService.submit(runnable)));
        } finally {
            executorService.shutdown();
        }
    }

And my WaitingTask is:

public class WaitingTask<T> extends Task<Void> {
    @Getter
    private final Future<T> future;

    public WaitingTask(Future<T> future) {
        this.future = future;
    }

    @Override
    protected Void call() {
            showSpinner();
            while (true) {
                if (future.isDone()) {
                    hideSpinner();
                    break;
                }
            }
        }
        return null;
    }
}

That works awesome - my app shows waiting spinner, and task runns in separate thread.

So I try to work the same way with Callable to get the result:

public static <T> T showWaiting(Callable<T> callable) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        try {
            FutureTask<T> task = new FutureTask<>(callable);
            Future<T> result = (Future<T>) executorService.submit(task);
            executorService.submit(new WaitingTask<>(result));
            return result.get();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            executorService.shutdown();
        }
    }

but I can not see waiting spinner, maybe the app's main thread waits for result.get(); and the app freezes. How can I fix it?

JeSa
  • 559
  • 4
  • 11
  • You can't retrieve the result directly in the JavaFX Thread without freezing. You have to work with callbacks (or something similar) – Felix May 17 '19 at 11:24
  • The issue is that FutureTask#get is a blocking method. You're starting the Callable on the executorService/thread but then you're blocking the main thread with the `result.get()` call. – kendavidson May 17 '19 at 12:02
  • @kendavidson yes I realized it (in the last paragraph), ideas to fix?)) – JeSa May 17 '19 at 12:42
  • Oh wow, sorry! It's been a morning. The way you're doing it, you would need to implement the same `while(true) { if future.isDone() { ... } }` logic in your showWaiting method, that you have in your WaitingTask. I think you're going a little overboard though, as you can just use one task, with a Spinners properties bound to the tasks properties. – kendavidson May 17 '19 at 12:48
  • @kendavidson thnx, but it's still the same – JeSa May 17 '19 at 13:58
  • Sorry, I should have added, you need to be doing that in yet another thread. Since if you're doing it right in the method (just replacing result.get()) then you're effectively doing the same blocking. Is there any reason you can't use the task to do this, as it takes care of the correct threading for you using the appropriate callbacks? – kendavidson May 17 '19 at 14:25
  • I just don't understand how to do this(( I can use tasks and callbacks – JeSa May 17 '19 at 14:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/193530/discussion-between-kendavidson-and-jesa). – kendavidson May 17 '19 at 14:41
  • You should be using a [`javafx.concurrent.Task`](https://openjfx.io/javadoc/12/javafx.graphics/javafx/concurrent/Task.html) if you want to compute something on a background thread and get the result on the FX thread. – Slaw May 17 '19 at 15:28
  • @Slaw thnx, I already use it, you can see it in the code above – JeSa May 21 '19 at 07:19
  • I actually missed that, but you aren't using it correctly. I added an answer. – Slaw May 21 '19 at 15:05
  • @kendavidson thanks for your help, I've posted the solution below – JeSa May 22 '19 at 08:52

2 Answers2

3

There are a few things you are doing incorrectly:


  1. You wrap your Callable in a FutureTask before submitting it to an ExecutorService. You don't need to do this, and in fact you shouldn't do this. Instead, just submit your Callable directly and you will get a Future in return.

    Future<T> future = executor.submit(callable);
    

    If you're using the core implementation of ExecutorService the returned Future will be a FutureTask anyway. Not that you should care—the only important thing is that its a Future. Note the same goes for Runnables; just submit them directly, don't wrap them in a FutureTask first.


  1. You're submitting your Callable, getting a Future, and wrapping said Future in a Task...and then submitting your Task. This means you will have two tasks for every one you want to execute. Depending on how your ExecutorService is configured, this equates to using two threads per task.

    You should be using your Task as if it was your Callable. Do the work inside the Task#call() method and return the result. Then only submit the Task, don't wrap it in anything first.

    executor.execute(task); // Don't need the Future here, just use "execute"
    

    If you want the result of the Task you can register callbacks (see this). The class is designed to invoke these callbacks on the JavaFX Application Thread.

    task.setOnSucceeded(event -> {
        T value = task.getValue();
        // do something with value...
    });
    

    Note that Task extends FutureTask. This seems contradictory to point 1, but that's just how it is. Personally, I wouldn't have designed the class that way—it ends up wrapping the Task in another Future (likely FutureTask) when executed using the Executor Framework.


  1. This is related to number 2; if you fix that issue then this issue inherently goes away.

    You are spin waiting for the wrapped Future to complete. This is a waste of resources. The Future interface has a get() method that will block the calling thread until said Future is done. If the Future completes normally you'll get the value in return, else if it completes exceptionally an ExecutionException will be thrown. The third option is the calling thread is interrupted and an InterruptedException is thrown.


  1. If the method names "showSpinner" and "hideSpinner" aren't misleading, you are updating the UI from a background thread. Never update the UI from a thread other than the JavaFX Application Thread. Now, you could wrap those calls in a Platform.runLater action, but you could also use the properties/callbacks of the Task. For instance, you could listen to the running property to know when to show and hide your spinner.

Taking all that into account, your example should look more like:

// Doesn't have to be an anonymous class
Task<String> task = new Task<>() {

    @Override 
    protected String call() {
        System.out.println("test");
        return "test2";
    }

});
task.runningProperty().addListener((obs, wasRunning, isRunning) -> {
    if (isRunning) {
        showSpinner();
    } else {
        hideSpinner();
    }
});
task.setOnSucceeded(event -> {
    String a = task.getValue();
    // Do something with value.
});

executorService.execute(task);

For more information, I suggest reading:

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thanks! About p.2 - I can't make something like `task.setOnSucceeded(event -> { final T value = task.getValue(); return value; });` and that's what I really need – JeSa May 21 '19 at 15:56
  • The `Worker` interface and its implementations are meant to do work on a background thread while reporting information, including the value, back to the _JavaFX Application Thread_. You never want to block the FX thread waiting for a result—your application will become unresponsive. If you need to do something after the task has succeeded, execute that code in the `onSucceeded` handler. That said, _if you're on a background thread and want to wait for the task's result_, you can use `Task#get` (remember, `Task` extends `FutureTask`). But then why use a `Task` in the first place? – Slaw May 21 '19 at 16:18
  • yes, I just want to use `Task#get`, but that "freezes" the app, and that's ok. I just want user seeing the spinner while waiting, not the unresponsive app (that can cause some panick) – JeSa May 22 '19 at 07:29
  • I'm confused. You say, "_that 'freezes' the app, and that's ok_", but then follow that up with, "_want user seeing the spinner while waiting, not the unresponsive app_". An unresponsive app **is** a frozen app. If you don't want the UI to be frozen, you **cannot** call `Task#get`—or any other blocking call—on the FX thread. Why do you need to wait for the result in the first place? Why not just do whatever needs to be done in the callback? – Slaw May 22 '19 at 07:37
  • I'll try to explain: I want to have a wrapper for "heavy" processes For example, let's take database stuff First is runnable - update 100500 rows ignoring result So I can call in code: `CommonHelper.showWaiting(() -> getDao().updateAllRows());` Second is callable - get 100500 rows So I can call in code: `List result = CommonHelper.showWaiting(() -> getDao().getAllPersons());` – JeSa May 22 '19 at 07:59
  • Maybe you are confused because I can't describe normally in english, sorry:( I want to say that for user (UI) there is a defference: he sees a spinner(and all is OK) or he sees an unresponsive app while waiting – JeSa May 22 '19 at 07:59
  • If the FX thread is blocked waiting for `Task#get` the **spinner will be frozen**. The FX thread is responsible for updating UI state and scheduling the next frame render. If it is stuck in some blocking call _none of that can happen_. The whole point of using concurrency in a GUI application is to avoid such a situation. Blocking until the background task finishes completely negates the benefits of using a separate thread in this context. When it comes to concurrency and GUI applications you need design your code using callbacks; when a task finishes, it _notifies_ you—you don't wait for it. – Slaw May 22 '19 at 08:32
  • thanks for your help, I've posted the solution below – JeSa May 22 '19 at 08:51
0

Thanks all for help, especially @Slaw and @kendavidson Finally I've found a simple and perfect solution here: Modal JaxaFX Progress Indicator running in Background

Maybe I'll post my full generic-based example here, based on this principles

JeSa
  • 559
  • 4
  • 11