Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the JavaFX WebView threading model

I am trying to understand how the JavaFX threads work. Descriptions here are not helping much.

For example in code below, the order of printing is always A then B then Z, which suggests that both the load() call and the code inside changed() are running on the same thread.

But I do not understand, why will the thread not just exit after the last Thread.sleep(2000) (since there is no more work to be done)?

I would expect the code inside changed() to run on a new thread everytime, and am utterly confused as to how this works!

public class Test extends Application {

  @Override
  public void start(Stage stage) throws Exception {

    final WebView webView = new WebView();
    Scene scene = new Scene(webView);
    stage.setScene(scene);
    stage.show();

    webView.getEngine().getLoadWorker().stateProperty()
        .addListener(new ChangeListener<State>() {
      @Override
      public void changed(ObservableValue<? extends State> ov, State t, State t1) {
        if (t1 == Worker.State.SUCCEEDED) {
          System.out.println("Z"); // <---
        }
      }
    });

    webView.getEngine().load("http://www.google.com");

    System.out.println("A"); // <---
    Thread.sleep(2000);
    System.out.println("B"); // <---
    Thread.sleep(2000);
  }

  public static void main(String[] args) {
    launch(args);
  }
}
like image 708
Moeb Avatar asked Oct 03 '22 07:10

Moeb


1 Answers

Short answer

start() and changed() run from the "JavaFX Application Thread". This thread is responsible for monitoring the UI for events, dispatching them to user code and repainting the UI. Unlike Runnable.run() that when exits, the thread terminates, start() is called by the "JavaFX Application Thread" in something like a loop: when start() ends, the next iteration is executed. (Actually the iterations run asynchronously/wait for events, so as not to eat all CPU resources, but the loop is OK as a simplified model.)

Longer answer

I'll try to explain in a little more detail.

Place print statements as (unindented to stand out):

    public void start(Stage stage) throws Exception {
System.out.println("2: " + Thread.currentThread().getName());
        ...
        webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
                    @Override
                    public void changed(ObservableValue<? extends State> ov, State t, State t1) {
                        if (t1 == Worker.State.SUCCEEDED) {
System.out.println("3: " + Thread.currentThread().getName());
                            System.out.println("Z"); // <---
                        }
                    }
                });

        webView.getEngine().load("...");

        System.out.println("A"); // <---
        Thread.sleep(2000);
        System.out.println("B"); // <---
        Thread.sleep(2000);
    }

    public static void main(String[] args) {
System.out.println("1: " + Thread.currentThread().getName());
        launch(args);
System.out.println("4: " + Thread.currentThread().getName());
    }

Observe the console and the application:

User action: Launch the app
Console: The following comes out immediately:

1: main
2: JavaFX Application Thread

Application: The main window appears... however without the content! Why? Because the "JavaFX Application Thread", that is responsible for painting the UI, is sleep()-ing, before point "A".

Console: "A" is printed after 2 seconds
Application: Still sleeping for "B", still no content in the window

Console: "B" is printed after 2 seconds
Application: Things come to life, the content starts filling; the UI thread is no longer blocked in sleep() so it is free to do its work.

Notice that despite the fact that start() is over, launch() is NOT, so the main thread is alive and well, thus the application is active. After a while, the page is loaded:

Console:

3: JavaFX Application Thread
Z

User action: Press close
Console:

4: main

Application: The window closes, the application exits

To gain more insight in this topic you can place breakpoints in main() and start() and observe the various threads that are active.

Comment: The observed behaviour from the sleep() calls in the UI thread is why you must never perform long-running tasks inside the UI thread, if you want the application to be responsive.

like image 127
Nikos Paraskevopoulos Avatar answered Oct 11 '22 02:10

Nikos Paraskevopoulos