简单的方法:阻塞后台线程直到更新完成:
您需要在 FX 应用程序线程上更新 UI。通常,您通过将一个普通的Runnable 传递给Platform.runLater(...) 来做到这一点。
如果您想在继续之前等待该 ui 更新完成,请创建一个 FutureTask 并将其传递给 Platform.runLater(...)。然后你可以在FutureTask上调用get(),它会一直阻塞直到任务完成:
private void updateUI() throws InterruptedException {
// actual work to update UI:
FutureTask<Void> updateUITask = new FutureTask(() -> {
// code to update UI...
}, /* return value from task: */ null);
// submit for execution on FX Application Thread:
Platform.runLater(updateUITask);
// block until work complete:
updateUITask.get();
}
这让FutureTask 处理所有等待和通知的棘手工作:在可能的情况下,最好使用更高级别的 API 来完成此类工作。
如果您愿意,可以将其重构为实用方法,类似于 Dainesch 的回答:
public class FXUtils {
public static void runAndWait(Runnable run) throws InterruptedException {
FutureTask<Void> task = new FutureTask<>(run, null);
Platform.runLater(task);
task.get();
}
}
替代方法:确保在任何帧渲染期间不消耗超过一个更新,如果更新未决,则阻塞后台线程
这里是一个有些不同的方法。创建一个容量为1 的BlockingQueue 来保存更新UI 的Runnables。从您的后台线程,将Runnables 提交到阻塞队列:由于阻塞队列最多可以容纳一个元素,如果一个元素已经挂起,这将阻塞。
要实际执行队列中的更新(并删除它们,以便可以添加更多),请使用AnimationTimer。这看起来像:
private final BlockingQueue<Runnable> updateQueue = new ArrayBlockingQueue<>(1);
后台线程代码:
// do some computations...
// this will block while there are other updates pending:
updateQueue.put(() -> {
// code to update UI
// note this does not need to be explicitly executed on the FX application
// thread (no Platform.runLater()). The animation timer will take care of that
});
// do some more computations
创建计时器以使用更新:
AnimationTimer updateTimer = new AnimationTimer() {
@Override
public void handle(long timestamp) {
Runnable update = updateQueue.poll();
if (update != null) {
// note we are already on the FX Application Thread:
update.run();
}
}
};
updateTimer.start();
这基本上确保了在任何时候都不会安排超过一个更新,后台线程会阻塞,直到任何待处理的更新被消耗。动画计时器检查(不阻塞)每个帧渲染的未决更新,确保执行每个更新。这种方法的好处是您可以增加阻塞队列的大小,有效地保留待处理更新的缓冲区,同时仍然确保在任何单帧渲染期间不会消耗超过一个更新。如果偶尔的计算需要比其他时间更长的时间,这可能会很有用;它使这些计算有机会在其他计算等待执行时进行计算。