简单的方法:阻塞后台线程直到更新完成:
您需要更新 FX 应用程序线程上的 UI。通常,您可以通过传递一个简单的Runnable
to Platform.runLater(...)
.
如果您想等待 ui 更新完成后再继续,请创建一个FutureTask http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html并将其传递给Platform.runLater(...)
。然后你可以打电话get()
on the FutureTask
,这将阻塞直到任务完成:
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();
}
}
替代方法:确保在任何帧渲染期间消耗的更新不超过一次,如果更新待处理,则阻塞后台线程
这是一种略有不同的方法。创建一个BlockingQueue
容量为1
持有Runnable
更新 UI。从您的后台线程中,提交Runnable
s 到阻塞队列:由于阻塞队列最多只能容纳一个元素,因此如果已经有一个元素处于挂起状态,则会阻塞。
要实际执行队列中的更新(并删除它们,以便可以添加更多更新),请使用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();
这基本上确保了任何时候都不会安排超过一个更新,并且后台线程会阻塞,直到消耗完任何挂起的更新为止。动画计时器检查(无阻塞)每个帧渲染上的挂起更新,确保执行每个更新。这种方法的好处是,您可以增加阻塞队列的大小,有效地保留待处理更新的缓冲区,同时仍然确保在任何单帧渲染期间消耗的更新不超过一个。如果偶尔有比其他计算花费更长时间的计算,这可能会很有用;它使这些计算有机会在其他计算等待执行时进行计算。