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

Joh*_*ian 4 java concurrency javafx 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错误.

如果我替换TableViewListView,则会发生预期的错误.

情况#1的示例代码:TableView从不同的线程更新a ,没有调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

情况#2的示例代码:ListView从不同的线程更新a ,没有调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

aw-*_*ink 7

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

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

来自Task-API-Doc

返回ObservableList的任务

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

还有另一个提示:

返回部分结果的任务

有时您想创建一个将返回部分结果的任务.也许您正在构建一个复杂的场景图,并希望在构建时显示场景图.或者您可能正在通过网络读取大量数据,并希望在数据到达时在TableView中显示条目.在这种情况下,FX Application Thread和后台线程都有一些共享状态.必须非常小心,永远不要从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();
        }
    }

}
Run Code Online (Sandbox Code Playgroud)