javamultithreadingjavafxjavafx-8

Populate control without flooding FXThread


I have the following situation: Large file with a lot of lines (~100k, logs from server). Each line in this file should be parsed, filtered and displayed on UI.

To read data from the file I use BufferedReader, which reads lines, parses them and prepare them for display. It runs on different thread (THREAD-1) and populates BlockingQueue. In another thread (THREAD-2) runs UIUpdater - its purpose is to get line batch from queue and run something line this:

Platform.runLater(() -> logArea.append(batchedLine)); 

Obviously, FX Thread floods and UI freezes. So, question is: where I can get information about patterns/best practices to resolve this issue?


Solution

  • It really depends on the control that you want to populate.

    Adding lots of nodes to the scene-graph is expensive therefore it will be slow (for example putting Text objects to any container).

    I would suggest the usage of a control that was originally designed to display a huge amount of data, like ListView.

    In the example, even during the update of the ListView the Button is responsive.

    Main.java

    public class Main extends Application {
    
        @Override
        public void start(Stage primaryStage) {
    
            HBox root = new HBox();
            Scene scene = new Scene(root, 700, 400, Color.WHITE);
    
            TableView<Person> personsTable = new TableView<Person>();
    
            TableColumn<Person, String> nameCol = new TableColumn<Person, String>("Name");
            nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
            personsTable.getColumns().add(nameCol);
    
            ObservableList<Person> persons = FXCollections.observableArrayList();
    
            Thread th = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    for (int i = 0; i < 100000; i++) {
                        Person person = new Person();
                        person.setName("Name" + i);
                        person.setAddress("Address" + i);
                        person.setCountry("Country" + i);
                        person.setCourse("Course" + i);
                        persons.add(person);
                        try {
                            Thread.sleep(5);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
    
                    }
    
                }
            }) ;
    
            th.start();
    
            personsTable.setItems(persons);
    
            Button b = new Button();
            b.setOnAction(new EventHandler<ActionEvent>() {
    
                @Override
                public void handle(ActionEvent event) {
                    System.out.println("I am printing independently of Person update!");
    
                }
            });
            root.getChildren().addAll(personsTable, b);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Person.java

    public class Person {
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        public String getCountry() {
            return country;
        }
    
        public void setCountry(String country) {
            this.country = country;
        }
    
        public String getCourse() {
            return course;
        }
    
        public void setCourse(String course) {
            this.course = course;
        }
    
        private String name;
        private String address;
        private String country;
        private String course;
    
    }
    

    User jewelsea has made a really good example on lgging. With little tailoring it could solve your issue.