【问题标题】:Need clarification on changing data in javafx application thread需要澄清在 javafx 应用程序线程中更改数据
【发布时间】:2019-04-17 12:43:43
【问题描述】:

我一直在将我的一个项目迁移到 JavaFX 并开始遇到线程问题。我将附上一个简短的例子。经过大量搜索,我设法解决了问题。我无法在 fx 应用程序线程之外更改 tableView 数据。我将我的代码从使用 SwingWorker 切换到了 Task。

起初,这一直有效,直到我向表的 observableList 添加了一个更改侦听器。然后我收到错误“不在 FX 应用程序线程上;”

当我尝试更新标签的值时,onChanged 方法内部发生了错误。我通过将它包装在 Platform.runLater() 中解决了这个问题。

我只是对为什么更改标签表示它不在应用程序线程上感到困惑。这是在哪个线程上运行的?另外,我是否通过使用任务正确地将行添加到我的表中?在我的实际应用程序中,我可能会添加 50k 行,因此为什么要使用单独的线程以免锁定 UI。

public class Temp extends Application{  
    private ObservableList<String> libraryList = FXCollections.observableArrayList();

    public void start(Stage stage) {

        Label statusLabel = new Label("stuff goes here");

        TableView<String> table = new TableView<String>(libraryList);
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<String, String> col = new TableColumn<String, String>("Stuff");
        col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
        table.getColumns().add(col);

        libraryList.addListener(new ListChangeListener<String>() {
            public void onChanged(Change change) {
                // Problem was caused by setting the label's text (prior to adding the runLater)
                Platform.runLater(()->{
                    statusLabel.setText(libraryList.size()+" entries");
                });                 
            }               
        });

        // dummy stuff
        libraryList.add("foo");
        libraryList.add("bar");

        Button b = new Button("Press Me");
        b.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent e) {
                FileTask task = new FileTask();
                new Thread(task).start();
            }
        });

        BorderPane mainBody = new BorderPane();

        mainBody.setTop(statusLabel);
        mainBody.setCenter(table);
        mainBody.setBottom(b);
        Scene scene = new Scene(mainBody);
        stage.setScene(scene);
        stage.show();       
    }


    class FileTask extends Task<Boolean>{

        public FileTask(){

        }

        protected Boolean call() throws Exception{

            Random rand = new Random();
            for(int i = 0; i < 5; i++) {
                String s = ""+rand.nextInt(Integer.MAX_VALUE);
                libraryList.add(s);
            }

            return true;
        }   
    }     

    public static void main(String[] args) {
        Application.launch(args);

    }
}

【问题讨论】:

  • 两个注意事项:(1)您可以使用Thread.currentThread()确定当前线程,(2)监听器由进行更改的线程调用。

标签: java multithreading javafx


【解决方案1】:

它按预期工作,你有应用程序线程和任务线程,它们看起来像这样:

App ------\ ----------------------
Task       \-label.setText() Exception

除了 App 线程之外,您无法对任何 UI 进行任何操作,因此添加 RunLater 即可:

App ----\ -------------/ RunLater(label.setText()) ----------
Task     \-add to list/

效果很好。有几种方法可以根据您的需要进行管理:

  • 如果您想更新任务中的表格列表,您可以将 RunLater 调用移动到任务内部,而不是处理程序内部,这样它仍然会让您回到 App 线程。这样,如果您实际上在应用线程上,则无需在处理程序中调用 RunLater。
App ---\ -----------------------/ label.setText() ----------
Task    \-RunLater(add to list)/
  • 另一个选项是只使用一个任务>,它将在另一个线程上运行,并返回要添加的字符串的完整列表。如果您在任务中进行网络调用,获取项目列表,然后在将它们全部下载到表中后添加它们,这更有可能是您想要的。
App -----\ ------------------------------/ label.setText() ---/ add to table list-------
Task      \-build list, update progress /- return final list /

希望格式保持不变。

【讨论】:

    【解决方案2】:

    考虑将视图所需的信息封装在一个单独的类(通常称为模型)中。 视图应通过侦听器或绑定来响应模型中的更改。
    您可以使用一个或多个线程来更新模型:

    import java.util.Random;
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.binding.Bindings;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ReadOnlyStringWrapper;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class Temp extends Application{
    
        @Override
        public void start(Stage stage) {
    
            Model model = new Model();
    
            Label statusLabel = new Label("stuff goes here");
    
            TableView<String> table = new TableView<>(model.getLibraryList());
            table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
    
            TableColumn<String, String> col = new TableColumn<>("Stuff");
            col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
            table.getColumns().add(col);
            statusLabel.textProperty().bind(Bindings.concat(model.sizeProperty.asString(), " entries"));
    
            // dummy stuff
            model.add("foo");  model.add("bar");
    
            Button b = new Button("Press Me");
            b.setOnAction(e -> {
                FileTask task = new FileTask(model);
                new Thread(task).start();
            });
    
            BorderPane mainBody = new BorderPane();
    
            mainBody.setTop(statusLabel);
            mainBody.setCenter(table);
            mainBody.setBottom(b);
            Scene scene = new Scene(mainBody);
            stage.setScene(scene);
            stage.show();
        }
    
        class Model {
    
            private final ObservableList<String> libraryList;
            private final IntegerProperty sizeProperty;
    
            Model(){
                libraryList = FXCollections.observableArrayList();
                sizeProperty = new SimpleIntegerProperty(0);
                libraryList.addListener((ListChangeListener<String>) change -> {
                    Platform.runLater(()->sizeProperty.set(libraryList.size()));
                });
            }
    
            //synchronize if you want to use multithread
            void add(String string) {
                Platform.runLater(()->sizeProperty.set(libraryList.add(string)));
            }
    
            ObservableList<String> getLibraryList() {
                return libraryList;
            }
    
            IntegerProperty getSizeProperty() {
                return sizeProperty;
            }
        }
    
        class FileTask implements Runnable{
    
            private final  Model model;
    
            public FileTask(Model model){
                this.model = model;
            }
    
            @Override
            public void run() {
                Random rand = new Random();
                for(int i = 0; i < 5; i++) {
                    String s = ""+rand.nextInt(Integer.MAX_VALUE);
                    model.add(s);
                }
            }
        }
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    }
    

    【讨论】:

    • 你也必须在 fx 线程上列出修改(不仅是 size 属性)
    猜你喜欢
    • 1970-01-01
    • 2017-11-26
    • 1970-01-01
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    相关资源
    最近更新 更多