JavaFX8 后台线程仍然干扰 GUI



我想编写一个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);

用于调用您的线程。

最新更新