JavaFX后台线程仍然干扰GUI

时间:2014-11-15 12:15:44

标签: java user-interface concurrency javafx-8 antlr4

我想编写一个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的步长调用update方法。

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();
    }
}

编辑:我在timeTask开始后删除了5秒的睡眠时间。这不是真正的问题。真正的问题是,只要logfileTask正在运行,timeTask就不会每秒更新标签。仅每1-5秒更新一次。只要logfileTask完成,timeTask就会完全按照应有的方式更新标签。这可能与Thread优先级有关吗? logfileTask产生相当大的负载...

编辑2:我无法通过其他方式来创建负载来重现问题。我只有解析器才有这些问题,解析器由ANTLR创建并创建一个巨大的解析树(许多对象)。但是,由于解析器是在后台线程中启动的,它如何干扰JavaFX Application线程?

编辑3:看起来确实是垃圾收集。我替换了日志文件解析,生成了一个巨大的数字列表,对它们进行排序并将它们再次丢弃,我可以看到与运行解析器时相同的间隙(不常见,但影响较大)

1 个答案:

答案 0 :(得分:1)

您在 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);

用于调用你的主题。