0

Can I be explained how updating GUI threads really work? It is all messy to me. For instance I want to update a ProgressBar in a loop. I got it that I need to put the loop into a new Task. I binded the progressBar's progressProperty to the Task's progress.

If I call the Task with

new Thread(task).start();

combined with the Task's updateProgress() method in the call function, it works fine, the progressBar is updated.

Question one : why do I fail at updating the ProgressBar by setting directly its progress inside of the loop (progressProperty being not binded) ? Same occurs if I want to set it (non-)visible inside of the loop.

Question 2 : Let the progressProperty be binded to the Task.progressProperty. Why can't I update the progressBar by calling the Task with Plateform.runLater(task) ? It won't update the GUI thread.

Question 3 : how do I set the visibility of the progressBar inside of the loop?

public class PRogressBar extends Application {

    ProgressBar progressBar;

    @Override
    public void start(Stage stage) {

        /**
            Task for updating the ProgressBar
        */
            Task<Void> task = new Task() {

                @Override
                protected Object call() throws Exception {
                    System.out.println("Init Task");
                    // progressBar.setVisible(false) // Question 3
                    // progressBar.setProgress(0.75) // question 1
                    for (int i = 1; i < 5; i++) {
                        final int update = i;
                        try {
                            Thread.sleep(500);
                            System.out.println("Run later : " + update/5.0);

                            updateProgress(i, 5);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    return null;
                }
            };

        /*
            Initializing the GUI
        */
        progressBar = new ProgressBar();
        Button button = new Button("blou");
        button.setOnAction((event) -> {
//            Platform.runLater(task); // Question 2

            Thread th = new Thread(task);
//            th.setDaemon(true);
             th.start();

             System.out.println("Thread started");



        });

        StackPane layout = new StackPane();
        layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
        HBox hbox = new HBox(progressBar, button);
        layout.getChildren().add(hbox);
        progressBar.setProgress(0.1);
//        setVisible2(false);

//        progressBar.progressProperty().bind(task.progressProperty());

        stage.setScene(new Scene(layout));
        stage.show();
}
jww
  • 97,681
  • 90
  • 411
  • 885
kaligne
  • 3,098
  • 9
  • 34
  • 60
  • [I would recommend reading this](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) – Jonathan Drapeau Aug 28 '14 at 13:00
  • You should just go ahead and use `SwingUtilities#invokeLater` as that's what I used(and worked fine) when I had to use a progress bar – Juxhin Aug 28 '14 at 13:09
  • thanks I am getting started. However it is about "Concurrency in Swing". I don't think JavaFX uses Swing? Does it matter? – kaligne Aug 28 '14 at 13:10
  • 1
    `JavaFX` is completely different from `Swing` – Juxhin Aug 28 '14 at 13:21
  • I tested using SwingUtilities.invokeLater, the Bar's progress is visually updated. However if I change the visibitlity in the run() method, I get an error saying ""AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0 ". It occurs when I want to change the visibility state of the ProgressBar inside the run() from Runnable, or inside call() from Task. – kaligne Aug 28 '14 at 13:25
  • I don't know JavaFX, but _most_ GUI frameworks are single-threaded. That is to say, they'll have an "event-dispatch" thread that calls application defined handler functions in response to GUI inputs; and they'll have many methods that must only be called from within that one thread. Usually there is something akin to Swing's `invokeLater(...)` that some other thread can use to post a request for a callback in the event thread. – Solomon Slow Aug 28 '14 at 13:53
  • The JavaFX equivalent of Swing's `invokeLater` is `Platform.runLater`. – Stuart Marks Aug 28 '14 at 13:57
  • 1
    possible duplicate of [Javafx: Difference between javafx.concurent and Platform.runLater?](http://stackoverflow.com/questions/18710039/javafx-difference-between-javafx-concurent-and-platform-runlater) – James_D Aug 28 '14 at 15:09
  • As suggested in [Concurrency in JavaFX, Cancelling the Task](http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm) you should properly handle _InterruptedException_ by checking _isCancelled_ and break the loop if it returns true. – Vertex Aug 29 '14 at 11:50
  • Only if you plan to interrupt it... – James_D Aug 29 '14 at 11:53

1 Answers1

2

Question one : why do I fail at updating the ProgressBar by setting directly its progress inside of the loop (progressProperty being not binded) ? Same occurs if I want to set it (non-)visible inside of the loop.

Becouse all changes of the UI that effects the current displayed Stage have to executed in JavaFX Application Thread. Your Task is executed in your own Thread, so make sure to call setProgress and setVisible in the JavaFX Application Thread. You have to make this by Platform.runLater(() -> { //change UI});

updateProgress and updateMessage are thread-safe. Maybe these methods effects the UI, e. g. if you have bind the progressProperty to a ProgressBar. You can call it from the worker thread in a safe manner. But updateProgress is the exception not the rule.

Question 2 : Let the progressProperty be binded to the Task.progressProperty. Why can't I update the progressBar by calling the Task with Plateform.runLater(task) ? It won't update the GUI thread.

Do you mean Platform.runLater(() -> myTask.call());? This should work, becouse call get executed in the JavaFX Application Thread, so you can make changes to the UI. But this is not the way you should work with Tasks :)

Question 3 : how do I set the visibility of the progressBar inside of the loop?

You are outside of the JavaFX Application Thread so you have to use Platform.runLater(...) for it.

By the way, Brian Goetz describes in his book Java Concurrency in Practice on Chapter 9.1 Why are GUIs single-threaded?.

Vertex
  • 2,682
  • 3
  • 29
  • 43
  • `Platform.runLater(myTask)`, or equivalently, `Platform.runLater(()-> myTask.call())` fails for exactly the same reason that it fails if you don't use a background thread at all. You're running the code on the FX Application Thread and blocking that thread: the UI can't be updated until the task has completed. – James_D Aug 29 '14 at 01:58
  • As @James_D sad, any blocking operations like `Thread.sleep`, reading/writing of files, reading/writing of network sockets, reading/writing a database or intensive computions should run outside of the JavaFX Application Thread otherwise the UI will be frozen. – Vertex Aug 29 '14 at 11:46
  • the Task you are referring to, "myTask", corresponds to my "task" right ? Because I can't access task.call(), `call() has protected access in Task where V is a type variable` .. What does it mean? I tried replacing `protected Object call()` by `public Object call()` but it does not work. – kaligne Aug 29 '14 at 11:50
  • Yes, sorry; you can't call `Platform.runLater(() -> myTask.call());`. I was just copying from the provided answer. The same argument applies to `Platform.runLater(task);` so you would not use this code anyway. Did you read the [answer linked above](http://stackoverflow.com/questions/18710039/javafx-difference-between-javafx-concurent-and-platform-runlater)? If that does not answer your question, please edit the question and explain how it is different. – James_D Aug 29 '14 at 11:55