0

I encounter a problem in developing javafx, I find latch has no effect in JavaFx, for example, in the following code:

public class JavafxLatchDemo1 extends Application {

    @Override
    public void start(Stage primaryStage) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        TextArea txtOut = new TextArea();

        StackPane root = new StackPane();
        root.getChildren().add(txtOut);

        Scene scene = new Scene(root, 300, 250);

        //invoke rpc function
        Callable<Integer> fibCall = new fibCallable(latch, txtOut);
        FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
        Thread fibThread = new Thread(fibTask);
        fibThread.start();
        latch.await(); //阻塞等待计数为0 


        txtOut.appendText("\n Say 'Hello World'");
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    } 
}

class fibCallable implements Callable<Integer>{        
    CountDownLatch latch;
    TextArea txtInput;

    fibCallable(CountDownLatch mylatch, TextArea txtIn){
        latch = mylatch;
        txtInput = txtIn;
    }

    @Override
    public Integer call() throws Exception {
        int temp1=1,temp2=0;

        System.out.println("Client will pay money for eshop");

        for(int i=0; i<10; i++){
            temp1 = temp1 + temp2;
            temp2 = temp1;
        }
        System.out.println("Client already decide to pay money for eshop");

        Platform.runLater(()->{
            txtInput.appendText("\nWhy, am I first?");

        });
        latch.countDown(); //计数减1 

        return (new Integer(temp1));
    }  
}

Since I set a latch to stop JavaFx main thread in latch.await();, and want the callable thread fibCallable output the content first: so I expect:

Why, am I first?
 Say 'Hello World'

but the real output is opposite:

Say 'Hello World'
Why, am I first?

why? and a solution?

James_D
  • 201,275
  • 16
  • 291
  • 322

1 Answers1

2

Platform.runLater() submits a runnable to be executed on the FX Application Thread. The start() method is also executed on the FX Application Thread. So the Runnable you submitted with Platform.runLater() cannot be executed until anything already executing on that thread completes.

So you start your fibThread in the background and then immediately wait for the latch: this blocks the FX Application Thread. The fibThread does a little bit of work and then submits a call to the (blocked) FX Application Thread. Then the fibThread releases the latch: the currently-blocked FX Application thread unblocks and finishes the current method call (appending the text "Say Hello World" to the text area and displaying the stage), and at some point after that the runnable submitted to Platform.runLater() executes on the same thread.

A "quick and dirty" fix is simply to wrap the second call to txtOut.appendText(...) in another Platform.runLater():

Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));

This is guaranteed to work, because runnables passed to Platform.runLater() are guaranteed to be executed in the order in which they are passed, and the countdown latch establishes a "happens-before" relationship between the two calls to Platform.runLater().

Note however that you are blocking the FX Application Thread with the call to latch.await(), which is bad practice (and will delay the display of the stage until the background thread completes). You should really put the call to latch.await(), along with the second Platform.runLater() in another background thread. Also note that you don't really need the latch at all, as you already have a FutureTask, and you can just wait for its result (this will be equivalent to waiting for the latch). So you can do

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class JavafxLatchDemo1 extends Application {

    @Override
    public void start(Stage primaryStage) throws InterruptedException {
//        CountDownLatch latch = new CountDownLatch(1);
        TextArea txtOut = new TextArea();

        StackPane root = new StackPane();
        root.getChildren().add(txtOut);

        Scene scene = new Scene(root, 300, 250);

        //invoke rpc function
//        Callable<Integer> fibCall = new fibCallable(latch, txtOut);
        Callable<Integer> fibCall = new fibCallable(txtOut);
        FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
        Thread fibThread = new Thread(fibTask);
        fibThread.start();
//        latch.await(); //阻塞等待计数为0 


        new Thread(() -> {
            try {
                // wait for fibTask to complete:
                fibTask.get();
                // and now append text to text area,
                // but this now must be done back on the FX Application Thread
                Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));
            } catch (Exception ignored) {
                // ignore interruption: thread is exiting anyway....
            }
        }).start();
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    } 

    class fibCallable implements Callable<Integer>{        
//        CountDownLatch latch;
        TextArea txtInput;

        fibCallable(TextArea txtIn){
            txtInput = txtIn;
        }

        @Override
        public Integer call() throws Exception {
            int temp1=1,temp2=0;

            System.out.println("Client will pay money for eshop");

            for(int i=0; i<10; i++){
                temp1 = temp1 + temp2;
                temp2 = temp1;
            }

            System.out.println("Client already decide to pay money for eshop");

            Platform.runLater(()->{
                txtInput.appendText("\nWhy, am I first?");

            });
//            latch.countDown(); //计数减1 

            return (new Integer(temp1));
        }  
    }
}

Finally, note that JavaFX has a concurrency API of its own, that supports various callbacks on the FX Application Thread directly. This API usually means you can avoid getting your hands dirty with latches and locks, etc.

James_D
  • 201,275
  • 16
  • 291
  • 322