为什么我可以从非UI线程而不是ListView更新TableView?

时间:2016-07-13 00:23:12

标签: java javafx concurrency javafx-8

我在JavaFX(java版本1.8.0_91)中遇到了一些奇怪的东西。我的理解是,如果想要从单独的线程更新UI,则必须使用Platform.runLater(taskThatUpdates)javafx.concurrent包中的其中一个工具。

但是,如果我有一个TableView,我可以在其中调用.setItems(someObservableList),我可以从单独的帖子更新someObservableList并查看对TableView的相应更改,而不是预期Exception in thread "X" java.lang.IllegalStateException: Not on FX application thread; currentThread = X错误。

如果我将TableView替换为ListView,则会出现预期的错误。

情况#1的示例代码:从不同的线程更新TableView,没有调用Platform.runLater() - 并且没有错误。

public class Test extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        // Create a table of integers with one column to display
        TableView<Integer> data = new TableView<>();
        TableColumn<Integer, Integer> num = new TableColumn<>("Number");
        num.setCellValueFactory(v -> new ReadOnlyObjectWrapper(v.getValue()));
        data.getColumns().add(num);

        // Create a window & add the table
        VBox root = new VBox();
        Scene scene = new Scene(root);
        root.getChildren().addAll(data);
        stage.setScene(scene);
        stage.show();

        // Create a list of numbers & bind the table to it
        ObservableList<Integer> someNumbers = FXCollections.observableArrayList();
        data.setItems(someNumbers);

        // Add a new number every second from a different thread
        new Thread( () -> {
            for (;;) {
                try {
                    Thread.sleep(1000);
                    someNumbers.add((int) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

情况#2的示例代码:从不同的线程更新ListView而不调用Platform.runLater() - 会产生错误。

public class Test extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        // Create a list of integers (instead of a table)
        ListView<Integer> data = new ListView<>();

        // Create a window & add the table
        VBox root = new VBox();
        Scene scene = new Scene(root);
        root.getChildren().addAll(data);
        stage.setScene(scene);
        stage.show();

        // Create a list of numbers & bind the table to it
        ObservableList<Integer> someNumbers = FXCollections.observableArrayList();
        data.setItems(someNumbers);

        // Add a new number every second from a different thread
        new Thread( () -> {
            for (;;) {
                try {
                    Thread.sleep(1000);
                    someNumbers.add((int) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

请注意,唯一的区别是将data实例化为ListView<Integer>而不是TableView<Integer>

那么这里给出了什么?这是因为在第一个例子中调用了TableColumn::setCellValueFactory()吗? - 这是我的直觉。我想知道为什么一个不会导致错误而另一个错误,更具体地说是.setItems调用如何将数据绑定到视图的规则。

1 个答案:

答案 0 :(得分:6)

正如@James_D已经提到的,你不应该在任何其他线程上更新属于FX应用程序线程的东西。但是在您的示例中,您已经在另一个线程中更新了ObservableList。无论为什么这对TableView和ListView都不起作用,这是错误的做法。

如果要在支持ObservableLists上执行中间更新,请查看Task类。

来自Task-API-Doc

  

返回ObservableList的任务

     

因为ListView,TableView和其他UI控件和场景图   节点使用ObservableList,通常需要创建和   从Task返回一个ObservableList。当你不在乎显示   中间值,正确编写这样一个任务的最简单方法是   只需在call方法中构造一个ObservableList,然后   在任务结束时返回。

和另一个提示:

  

返回部分结果的任务

     

有时您想要创建一个将返回部分结果的任务。   也许你正在构建一个复杂的场景图并想要显示   正在构建的场景图。或许你正在读一篇文章   通过网络的大量数据,并希望显示条目   在数据到达时在TableView中。在这种情况下,有一些   共享状态可用于FX应用程序线程和   背景线程。必须非常小心永远不要更新共享   从FX应用程序线程以外的任何线程状态。

     

最简单的方法是利用   updateValue(Object)方法。可以从中重复调用此方法   后台线程。更新被合并以防止饱和   FX事件队列。这意味着你可以像你一样经常打电话   喜欢从后台线程,但只有最新的集合   最终确定。

用于对TableView和ListView的ObservableLists进行中间更新的此类Task的示例是:

import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class IntermediateTest extends Application {

    @Override
    public void start(Stage primaryStage) {

        TableView<Integer> tv = new TableView<>();
        TableColumn<Integer, Integer> num = new TableColumn<>("Number");
        num.setCellValueFactory(v -> new ReadOnlyObjectWrapper(v.getValue()));
        tv.getColumns().add(num);
        PartialResultsTask prt = new PartialResultsTask();
        tv.setItems(prt.getPartialResults());

        ListView<Integer> lv = new ListView<>();
        PartialResultsTask prt1 = new PartialResultsTask();
        lv.setItems(prt1.getPartialResults());
        new Thread(prt).start();
        new Thread(prt1).start();

        // Create a window & add the table
        VBox root = new VBox();
        root.getChildren().addAll(tv, lv);
        Scene scene = new Scene(root, 300, 450);

        primaryStage.setTitle("Data-Adding");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

    public class PartialResultsTask extends Task<ObservableList<Integer>> {

        private ReadOnlyObjectWrapper<ObservableList<Integer>> partialResults
                = new ReadOnlyObjectWrapper<>(this, "partialResults",
                        FXCollections.observableArrayList(new ArrayList()));

        public final ObservableList getPartialResults() {
            return partialResults.get();
        }

        public final ReadOnlyObjectProperty<ObservableList<Integer>> partialResultsProperty() {
            return partialResults.getReadOnlyProperty();
        }

        @Override
        protected ObservableList call() throws Exception {
            updateMessage("Creating Integers...");
            Random rnd = new Random();
            for (int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                if (isCancelled()) {
                    break;
                }
                final Integer r = rnd.ints(100, 10000).findFirst().getAsInt();
                Platform.runLater(() -> {
                    getPartialResults().add(r);
                });
                updateProgress(i, 10);
            }
            return partialResults.get();
        }
    }

}