需要澄清更改javafx应用程序线程中的数据吗

需要澄清更改javafx应用程序线程中的数据吗,java,multithreading,javafx,Java,Multithreading,Javafx,我一直在将我的一个项目迁移到JavaFX,并开始遇到线程问题。我将附上一个简短的例子。经过多次搜索,我终于解决了这个问题。我无法在fx应用程序线程之外更改tableView数据。我将代码从使用SwingWorker转换为任务 起初,在我将更改侦听器添加到表的observableList之前,这是有效的。然后我收到错误“不在FX应用程序线程上 当我试图更新标签的值时,onChanged方法内部发生了错误。我通过将其包装到Platform.runLater()中解决了这个问题 我只是不明白为什么更改

我一直在将我的一个项目迁移到JavaFX,并开始遇到线程问题。我将附上一个简短的例子。经过多次搜索,我终于解决了这个问题。我无法在fx应用程序线程之外更改tableView数据。我将代码从使用SwingWorker转换为任务

起初,在我将更改侦听器添加到表的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);

    }
}
公共类临时扩展应用程序{
私有ObservableList libraryList=FXCollections.observableArrayList();
公众假期开始(阶段){
标签状态标签=新标签(“东西在这里”);
TableView表格=新的TableView(libraryList);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_策略);
TableColumn col=新的TableColumn(“填充”);
col.setCellValueFactory(cellData->new ReadOnlyStringWrapper(cellData.getValue());
table.getColumns().add(col);
libraryList.addListener(新ListChangeListener(){
更改后的公共作废(更改){
//问题是由设置标签的文本(在添加runLater之前)引起的
Platform.runLater(()->{
statusLabel.setText(libraryList.size()+“条目”);
});                 
}               
});
//假东西
图书馆列表。添加(“foo”);
图书馆列表。添加(“栏”);
按钮b=新按钮(“按下我”);
b、 setOnAction(新的EventHandler(){
公共无效句柄(ActionEvent e){
FileTask任务=新建FileTask();
新线程(任务).start();
}
});
BorderPane主体=新建BorderPane();
mainBody.setTop(状态标签);
主体。设置中心(表);
主体.立根(b);
场景=新场景(主体);
舞台场景;
stage.show();
}
类FileTask扩展了任务{
公共文件任务(){
}
受保护的布尔调用()引发异常{
Random rand=新的Random();
对于(int i=0;i<5;i++){
字符串s=“”+rand.nextInt(整数最大值);
图书馆列表。添加(s);
}
返回true;
}   
}     
公共静态void main(字符串[]args){
应用程序启动(args);
}
}

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

App ------\ ----------------------
Task       \-label.setText() Exception
除了应用程序线程之外,您不能在任何东西上执行任何UI工作,因此添加RunLater可以执行以下操作:

App ----\ -------------/ RunLater(label.setText()) ----------
Task     \-add to list/
这很有效。有几种方法可以根据您的需要进行管理:

  • 如果要更新任务中的表列表,可以将RunLater调用移动到任务内部,而不是处理程序内部,这样它仍然会让您返回到应用程序线程。这样,如果您实际上在应用程序线程上,则无需在处理程序中调用RunLater
  • 另一个选项是只使用将在另一个线程上运行的Task>,并返回要添加的字符串的完整列表。如果您在任务中进行网络呼叫,获取项目列表,然后在所有项目下载到表中后添加它们,则这更可能是您想要的

希望格式保持不变。

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

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);
    }
}
import java.util.Random;
导入javafx.application.application;
导入javafx.application.Platform;
导入javafx.beans.binding.Bindings;
导入javafx.beans.property.IntegerProperty;
导入javafx.beans.property.ReadOnlyStringWrapper;
导入javafx.beans.property.SimpleIntegerProperty;
导入javafx.collections.FXCollections;
导入javafx.collections.ListChangeListener;
导入javafx.collections.ObservableList;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.Label;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableView;
导入javafx.scene.layout.BorderPane;
导入javafx.stage.stage;
公共类临时扩展应用程序{
@凌驾
公众假期开始(阶段){
模型=新模型();
标签状态标签=新标签(“东西在这里”);
TableView table=新的TableView(model.getLibraryList());
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_策略);
TableColumn col=新的TableColumn(“填充”);
col.setCellValueFactory(cellData->new ReadOnlyStringWrapper(cellData.getValue());
table.getColumns().add(col);
statusLabel.textProperty().bind(Bindings.concat(model.sizeProperty.asString(),“entries”);
//假东西
model.add(“foo”);model.add(“bar”);
按钮b=新按钮(“按下我”);
b、 设定动作(e->{
FileTask任务=新建FileTask(模型);
新线程(任务).start();
});
BorderPane主体=新建BorderPane();
mainBody.setTop(状态标签);
主体。设置中心(表);
主体.立根(b);
场景=新场景(主体);
舞台场景;
stage.show();
}
类模型{
App -----\ ------------------------------/ label.setText() ---/ add to table list-------
Task      \-build list, update progress /- return final list /
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);
    }
}