gpt4 book ai didi

multithreading - JavaFX TableView 高频率更新

转载 作者:行者123 更新时间:2023-12-03 12:59:53 25 4
gpt4 key购买 nike

我尝试频繁更新 JFX TableView(概念验证应用程序)中的单元格。我通过 FXML 加载 TableView 并启动 ExecutorService 来更改单元格的值。

当我启动应用程序时,我注意到该更新适用于前 3-4 百万个元素,然后就卡住了。如果我放慢更新速度(参见 MAGIC#1),它可以工作(10 毫秒仍然太快,但 100 毫秒延迟有效)。所以我认为这可能是一个线程问题。

但后来我发现,如果我将一个空的 ChangeListener(参见 MAGIC#2)添加到属性中,它可以正常工作。即使不需要 MAGIC#1。

难道我做错了什么?我是否必须以不同的方式更新单元格?

在此先感谢您的帮助!!

TableView 中的元素:

public class Element {
public static final AtomicInteger x = new AtomicInteger(0);
private final StringProperty nameProperty = new SimpleStringProperty("INIT");

public Element() {
// MAGIC#2
// this.nameProperty.addListener((observable, oldValue, newValue) -> {});
}

public void tick() {
this.setName(String.valueOf(x.incrementAndGet()));
}

public String getName() ...
public void setName(String name)...
public StringProperty nameProperty() ...
}

FXML 的 Controller :
public class TablePerformanceController implements Initializable {
private final ObservableList<Element> data = FXCollections.observableArrayList();

public Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
// MAGIC#1
// try { Thread.sleep(100); } catch (Exception e) {}
}
};

private ExecutorService executor = null;

@FXML
public TableView<Element> table;

@Override
public void initialize(URL location, ResourceBundle resources) {
this.table.setEditable(true);

TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
this.table.getColumns().addAll(nameCol);

this.data.add(new Element());
this.table.setItems(this.data);

this.executor = Executors.newSingleThreadExecutor();
this.executor.submit(this.changeValues);
}
}

最佳答案

您违反了 JavaFX 的单线程规则:只能从 FX 应用程序线程更新 UI。您的 tick()方法更新 nameProperty() ,并且由于表格单元格正在观察 nameProperty() , tick()导致 UI 更新。既然你调用 tick()从后台线程,对 UI 的更新发生在后台线程上。由此产生的行为本质上是未定义的。

此外,您的代码最终会收到太多更新 UI 的请求。因此,即使您修复了线程问题,您也需要以某种方式限制请求,以免 FX 应用程序线程因太多需要更新的请求而泛滥,这将使其无响应。

Throttling javafx gui updates 中介绍了执行此操作的技术。 .我将在表模型类的上下文中重复它:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Element {

// Note that in the example we only actually reference this from a single background thread,
// in which case we could just make this a regular int. However, for general use this might
// need to be threadsafe.
private final AtomicInteger x = new AtomicInteger(0);

private final StringProperty nameProperty = new SimpleStringProperty("INIT");

private final AtomicReference<String> name = new AtomicReference<>();


/** This method is safe to call from any thread. */
public void tick() {
if (name.getAndSet(Integer.toString(x.incrementAndGet())) == null) {
Platform.runLater(() -> nameProperty.set(name.getAndSet(null)));
}
}

public String getName() {
return nameProperty().get();
}

public void setName(String name) {
nameProperty().set(name);
}

public StringProperty nameProperty() {
return nameProperty;
}
}

这里的基本思想是使用 AtomicReference<String给不动产“遮蔽”。自动更新它并检查它是否为 null ,如果是这样,请在 FX 应用程序线程上安排对不动产的更新。在更新中,原子地检索“影子”值并将其重置为空,并将真实属性设置为检索到的值。这确保了在 FX 应用程序线程上更新的新请求仅在 FX 应用程序线程使用它们时发出,从而确保 FX 应用程序线程不会被淹没。当然,如果在 FX 应用程序线程上安排更新与实际发生更新之间存在延迟,则当更新确实发生时,它仍会检索设置“影子”值的最新值。

这是一个独立的测试,基本上等同于您展示的 Controller 代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class FastTableUpdate extends Application {

private final ObservableList<Element> data = FXCollections.observableArrayList();

public final Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
}
};

private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});



@Override
public void start(Stage primaryStage) {

TableView<Element> table = new TableView<>();
table.setEditable(true);

TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setPrefWidth(200);
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
table.getColumns().add(nameCol);

this.data.add(new Element());
table.setItems(this.data);

this.executor.submit(this.changeValues);

Scene scene = new Scene(table, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}

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

关于multithreading - JavaFX TableView 高频率更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36593572/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com