Enr*_*ico 8 java concurrency multithreading javafx javafx-11
在我的 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();
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
你好视图.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>
Run Code Online (Sandbox Code Playgroud)
我测量了从小组中移除不同数量的孩子所需的时间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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,所需的时间呈指数级增长。现在想象一下,您必须清除 UI 中的子级,并且用户必须在应用程序冻结时等待 2.5 分钟。此外,在这个简化的示例中,它只是一条简单的线,在“真实”应用程序中,它是一个更复杂的几何形状 -> 需要更多时间。
因此,另一个想法是将该组与其父窗格“解除绑定”。因为当它解除绑定时,我可以在另一个线程中将其删除。这意味着 1. 用户界面不会冻结,2. 速度会更快。这是尝试:
pane.getChildren().remove(group); // or clear()
// and then clear the group in another thread like above
Run Code Online (Sandbox Code Playgroud)
问题是:这种“解除绑定”也需要很多时间。不是2.5分钟,而是0.5分钟,还是太多了。
另一个想法是创建多个组,因为正如您所看到的,具有 10 000 或 100 000 个元素的组清除速度更快。这也失败了,因为几个组突然花费了更长的时间并且删除速度呈指数级增长。例如,第一个需要 20 秒,第二个需要 10 秒,第三个需要 5 秒,等等。
长话短说
是否有机会在单独的线程中删除该组的子级,或者比使用更快group.getChildren().clear()?我尝试了我想到的一切......
如果我在删除时只能显示加载栏,那会比仅仅冻结表面并等待 2 分钟要好......
我感谢每一个想法/帮助。
编辑,请参阅注释 没有 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));
}
}
Run Code Online (Sandbox Code Playgroud)
Sla*_*law 10
尚未对其进行测试,但这种情况下执行时间较长的根本原因可能已在 JavaFX 21+ 中使用JDK-8290765 - 删除父禁用/treeVisible 侦听器“修复” 。也就是说,我仍然认为拥有数百万个节点本身就是一个问题。节点太多了。
执行时间较长是因为 a 的每个子级都使用该 的和属性Parent注册了一个侦听器。JavaFX 目前的实现方式是,这些监听器存储在一个数组(即列表结构)中。添加侦听器的成本相对较低,因为新侦听器只是简单地插入到数组的末尾,偶尔会调整数组的大小。但是,当您从其中删除子级并删除侦听器时,需要线性搜索数组,以便找到并删除正确的侦听器。每个被移除的孩子都会发生这种情况。disabledtreeVisibleParentParent
因此,当您清除 的子列表时,Group您将触发这两个属性的 1,000,000 次线性搜索,总共导致 2,000,000 次线性搜索。更糟糕的是,要删除的侦听器要么(取决于删除子级的顺序)始终位于数组的末尾(在这种情况下,有 2,000,000 个最坏情况线性搜索),要么始终位于数组的开头数组,在这种情况下,有 2,000,000 次最佳情况线性搜索,但每次删除都会导致所有剩余元素必须移动一位。
至少有两种解决方案/解决方法:
不显示 1,000,000 个节点。如果可以的话,尝试只显示用户实际可以看到的数据的节点。例如,虚拟化控件在任何给定时间ListView仅TableView显示大约1-100个单元格(取决于各种因素,包括窗口大小、屏幕大小等)。
不要清除 的子级Group。相反,只需Group用新的替换旧的Group。Group如果需要,您可以在后台线程中准备新的。
这样做,在我的计算机上花了 3.5 秒创建了另一个Group包含 1,000,000 个子级的文件,然后Group用新的替换了旧的Group. 然而,由于需要立即渲染所有新节点,仍然存在一点滞后峰值。
如果您不需要填充新的,Group那么您甚至不需要线程。在这种情况下,我的计算机上的交换大约花费了 0.27 秒。
| 归档时间: |
|
| 查看次数: |
327 次 |
| 最近记录: |