我想编写一个JavaFX GUI,加载和解析一个更大的文件(22M),大约需要30秒。但是,当我在任务中开始解析时,GUI 的其余部分非常没有响应。然而,它并非完全没有反应,但它仍然时不时地挂起几秒钟。为了演示这一点,我编写了一个小型应用程序,该应用程序在进度条中显示解析进度,并并行执行另一个任务,该任务每秒更新一次标签。现在,第二个任务不应该被第一个任务阻止,所以我可能做错了。代码如下:
每秒滴答的任务:
class TimeTask extends Task<Void> {
@Override
protected Void call() throws Exception {
while (true) {
updateMessage(Instant.now().toString());
Thread.sleep(1000);
}
}
}
替换解析日志文件:更新方法在解析器的 100ms 步长中调用。
public class ParseLogfileTask extends Task<Void> {
private LinkedList<Integer> numbers = new LinkedList<Integer>();
private Random random = new Random();
@Override
protected Void call() {
Instant startInstant = Instant.now();
while (Duration.between(startInstant, Instant.now()).getSeconds() < 25) {
for (int i = 0; i < 10000000; i++) {
numbers.add((Integer) random.nextInt());
}
Collections.shuffle(numbers);
Collections.sort(numbers);
numbers.clear();
updateParseProgress(
(int) Duration.between(startInstant, Instant.now())
.toMillis(), 25000);
}
return null;
}
public void updateParseProgress(int currentIndex, int maxIndex) {
updateProgress(currentIndex, maxIndex);
}
}
应用程序的启动方法:
@Override
public void start(Stage primaryStage) {
try {
Group root = new Group();
Scene scene = new Scene(root, 400, 400);
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
ProgressBar bar = new ProgressBar();
Label label = new Label("new");
vbox.getChildren().addAll(bar, label);
root.getChildren().add(vbox);
primaryStage.setScene(scene);
primaryStage.show();
Task<Void> timeTask = new TimeTask();
Task<Void> logFileTask = new ParseLogfileTask();
bar.progressProperty().bind(logFileTask.progressProperty());
label.textProperty().bind(timeTask.messageProperty());
Platform.runLater(() -> {
new Thread(timeTask).start();
new Thread(logFileTask).start();
});
}
catch (Exception e) {
e.printStackTrace();
}
}
编辑:我在时间任务开始后删除了5秒的睡眠。对于真正的问题来说,这不是必需的。真正的问题是,只要日志文件任务正在运行,timeTask 就不会每秒更新标签。仅每 1-5 秒更新一次。一旦日志文件任务完成,时间任务就会完全按预期更新标签。这可能与线程优先级有关吗?日志文件任务产生相当重的负载...
编辑2:我无法使用其他方式创建负载来重现问题。我只有解析器有这些问题,它是由 ANTLR 创建的,并创建了一个巨大的解析树(许多对象)。但是,由于解析器是在后台线程中启动的,因此它如何干扰 JavaFX 应用程序线程?
编辑3:看起来它确实是垃圾收集。我将日志文件解析替换为生成一个巨大的数字列表,对它们进行排序并再次丢弃它们,我可以看到与解析器运行时相同的差距(不那么频繁,但影响更大)
你在 ParseLogfileTask 中调用的操作很多。就像一段时间(真的)。在返回 null 之前使用 Thread.sleep。
@Override
protected Void call() {
Instant startInstant = Instant.now();
while (Duration.between(startInstant, Instant.now()).getSeconds() < 25) {
for (int i = 0; i < 100000; i++) {
numbers.add((Integer) random.nextInt());
}
Collections.shuffle(numbers);
Collections.sort(numbers);
numbers.clear();
updateParseProgress(
(int) Duration.between(startInstant, Instant.now())
.toMillis(), 25000);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
}
return null;
}
所以它对我有用。
此外,最好使用
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(timeTask);
service.execute(logFileTask);
用于调用您的线程。