【发布时间】:2023-03-31 05:15:01
【问题描述】:
在我的 JavaFX 项目中,我使用了很多形状(例如 1 000 000)来表示地理数据(例如地块轮廓、街道等)。它们存储在一个组中,有时我必须清除它们(例如,当我加载包含新地理数据的新文件时)。 问题:清除/删除它们需要很多时间。 所以我的想法是在一个单独的线程中删除形状,由于 JavaFX 单线程,这显然不起作用。
这是我正在尝试做的简化代码:
HelloApplication.java
package com.example.javafxmultithreading;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
public static Group group = new Group();
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
for (int i = 0; i < 1000000; i++) {
group.getChildren().add(new Line(100, 200, 200, 300));
}
HelloController.helloController = fxmlLoader.getController();
HelloController.helloController.pane.getChildren().addAll(group);
}
public static void main(String[] args) {
launch();
}
}
HelloController.java
public class HelloController {
public static HelloController helloController;
@FXML
public Pane pane;
public VBox vbox;
@FXML
public void onClearShapes() throws InterruptedException {
double start = System.currentTimeMillis();
HelloApplication.group.getChildren().clear();
System.out.println(System.currentTimeMillis() - start);
Service<Boolean> service = new Service<>() {
@Override
protected Task<Boolean> createTask() {
return new Task<>() {
@Override
protected Boolean call() {
// Try to clear the children of the group in this thread
return true;
}
};
}
};
service.setOnSucceeded(event -> {
System.out.println("Success");
});
service.start();
}
}
你好-view.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="vbox" alignment="CENTER" prefHeight="465.0" prefWidth="711.0" spacing="20.0"
xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.javafxmultithreading.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Pane fx:id="pane" prefHeight="200.0" prefWidth="200.0"/>
<Button mnemonicParsing="false" onAction="#onClearShapes" text="Clear shapes"/>
</VBox>
我用group.getChildren().clear()测量了从组中删除不同数量的孩子所花费的时间:
amount of children | time
100 2ms = 0,002s
1 000 4ms = 0,004s
10 000 38ms = 0,038s
100 000 1273ms = 1,2s
1 000 000 149896ms = 149,896s = ~2,5min
如您所见,所需时间呈指数级增长。 现在想象一下,您必须在 UI 中清除子项,而用户必须等待 2.5 分钟等待应用程序冻结。 此外,在这个简化的示例中,它只是一条简单的线,在“真实”应用程序中,它是一个更复杂的几何图形 -> 需要更多时间。
因此,另一个想法是将组与它的父级窗格“解除绑定”。 因为当它解除绑定时,我可以在另一个线程中删除它。这意味着 1. 用户界面不会冻结 2. 它会更快。 这是尝试:
pane.getChildren().remove(group); // or clear()
// and then clear the group in another thread like above
问题:这种“解除绑定”也需要很多时间。不是 2.5 分钟,而是 0.5 分钟,这仍然是太多了。
另一个想法是创建多个组,因为如您所见,具有 10 000 或 100 000 个元素的组被清除得更快。 这也失败了,因为几个组突然需要更长的时间并且被删除的速度呈指数级增长。 例如,第一个需要 20 秒,第二个需要 10 秒,第三个需要 5 秒等。
长话短说
有没有机会在单独的线程中删除该组的孩子,或者比group.getChildren().clear() 更快?我尝试了所有想到的...
如果我只能在删除时显示一个加载栏,那比仅仅冻结表面并等待 2 分钟要好...
我感谢每一个想法/帮助。
编辑,见 cmets 没有 FXML 的简单示例:
import javafx.scene.Group;
import javafx.scene.shape.Line;
public class Test {
public static void main(String[] args) {
Group group = new Group();
System.out.println("adding lines");
for (int i = 0; i < 1000000; i++) {
group.getChildren().add(new Line(100, 200, 200, 300));
}
System.out.println("adding done");
System.out.println("removing starts");
double start = System.currentTimeMillis();
group.getChildren().clear();
System.out.println("removing done, needed time: " + (System.currentTimeMillis() - start));
}
}
【问题讨论】:
-
对
clear()的简单调用似乎不太可能真的需要那么长时间。 (这可能表明存在某种错误。)请注意,您不能从后台线程修改活动节点(作为场景图一部分的节点),所以我真的看不出线程在这里有什么帮助。您能否发布一个完整、简单的示例,我们可以通过简单的尝试来复制和运行(并且没有所有静态字段等)?我不明白为什么标准方法行不通。 -
“很多形状(例如 1 000 000)来表示地理数据” -> 只是我的意见,但我认为这不是一个好的设计。形状是为屏幕渲染而设计的节点。使用节点来表示此类数据而不是专门针对该类型数据的数据结构存在开销。我不了解 GIS 系统,但我确信它们具有使用 Java 绑定的数据表示形式,这对于此类数据比使用节点更有效。最终,您需要映射到可见节点,但不应为 100 万。
-
我只是不明白您为什么将
Group定义为公共和静态,并在Application类中,然后从那里操作控制器中的节点。这不是“正常”的 JavaFX(甚至 Java)编程风格。您从标准实践中获得的越多,您就越有可能陷入一些奇怪的极端情况,因为某些奇怪的原因会影响性能。 (虽然这似乎不太可能是这里的原因。)无论如何,我会在回到电脑前进行实验 -
@James_D 从分析来看,时间似乎都花在了
parent属性的invalidated()方法上(当一个孩子被移除时,父级设置为null)。特别是,从 parent 的treeVisible和disabled属性中删除侦听器似乎很昂贵。 -
有 1,000,000 个子属性,这意味着这些属性每个都至少注册了 1,000,000 个侦听器。这些侦听器存储在一个数组中,并在清除所有 1,000,000 个子项时一个接一个删除。这意味着这项工作至少每个属性需要进行 1,000,000 次线性搜索(当然,搜索时间会随着删除的子项的处理而减少,但仍然如此)。
标签: java multithreading javafx concurrency javafx-11