我有一个javaFX应用程序可视化计算几何算法。算法的执行发生在另一个线程中,我们称之为mainComputingThread
。
算法可以通过添加/删除/修改形状随时更新UI。
所以代码看起来像:
//do some computaions (1)
updateUI();
//do some more calculations (2)
我想知道的是在updateUI
方法中立即更新UI并阻止调用线程进一步运行(标记为(2))直到UI更新完成。
我想到了布尔警卫。所以代码看起来像:
updateUI(){
boolean guard = false;
Platform.runLater(new Runnable()
{
run(){
//do the actual update
guard = true;
}
});
while(guard==false);
}
我希望你能明白我的意思。如果有更好的解决方案可以解决这个问题,我真的很好奇......
答案 0 :(得分:6)
简单方法:阻止后台线程直到更新完成:
您需要更新FX应用程序线程上的UI。通常,您可以通过将普通Runnable
传递给Platform.runLater(...)
来执行此操作。
如果您想在继续操作之前等待ui更新完成,请创建FutureTask
并将其传递给Platform.runLater(...)
。然后,您可以在get()
上致电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}},以保存更新UI的1
。在后台线程中,将Runnable
提交到阻塞队列:由于阻塞队列最多只能容纳一个元素,因此如果一个元素已经挂起,这将阻止。
要实际执行队列中的更新(并删除它们,以便添加更多内容),请使用Runnable
。这看起来像:
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
这基本上确保了在任何时候都不会安排多于一个更新,后台线程会阻塞,直到消耗任何挂起的更新。动画计时器检查(不阻止)每个帧渲染的挂起更新,确保每次更新都执行。这种方法的好处在于,您可以增加阻塞队列的大小,有效地保留挂起的更新缓冲区,同时仍确保在任何单帧渲染期间不会消耗多于一个更新。如果偶尔计算花费的时间比其他计算时间长,这可能很有用;它使这些计算有机会在其他人等待执行时计算。
答案 1 :(得分:1)
编辑:
所以,我总是在原型中最快的方式如下,转换:
//do some computaions (1)
updateUI();
//do some more calculations (2)
到
ExecutorService executor = Executors.newFixedThreadPool(1);
class JobStep implements Runnable {
public void run() {
doSomeComputations();
Platform.runLater(() -> {
updateUI();
executor.submit(new JobStep());
});
}
executor.submit(new JobStep());
不是答案,而是建议如何解决问题。
根据我的经验,完整的解决方案将更加精细。我会将JavaFX形状实例与您的算法处理的形状分开。我会通过使用不同的类类型并在两者之间进行同步来实现。
图形算法的趋势比可视化算法快得多。如果算法在小数据集上运行,那么渲染通常会显着降低它的速度。通过使用和不使用可视化运行相同的算法可以很容易地看到它。
如果数据集大于最琐碎的数据集,则绘制单个帧可能需要超过一秒钟。交互式可视化预计会在&#34;实时&#34;中响应,最好每秒多次响应。
数据可视化设施有许多方法可以解决这个问题。最佳候选人总是包括:
简化可视化。绘制更简单的形状而不是复杂的形状,例如从框中移除圆角。 LOD(细节级别)也适用于这一点:在交互式滚动期间,可视化元素可能被边界框对应物替换。
选择性隐藏。仅绘制整个数据集的一部分。
并行化和硬件加速。 GPU本身提供了许多处理复杂可视化的方法。通常,低级编程API(OpenGL,着色器程序)允许比每个高级包装API更好的吞吐量,包括JavaFX
最常见的是,最终解决方案不仅包含上述要点,还包含其他要点,包括特定于域的优化。
可视化工具总是有很多限制,比如最常见的限制:必须在专用线程中更新(线程限制方法)。它们还带有可视化特定的数据结构。
从数据处理算法阶段,最常见的要求之一是它不能被可视化阻止或延迟。这些算法也是以一种风格编写的,它对于可视化手段并没有很好的转换:数据结构上的命令性循环而不是更新可观察的可绘制对象。但它有一个很好的理由:算法可以针对性能或内存消耗进行优化。
该问题的一种架构方法可能如下:
数据处理阶段在预定义的点生成快照。添加,修改和删除操作都作为此数据包发布。它可以只是正在处理的数据结构的副本,也可以是合并事件的形式。
数据处理和数据可视化在不同的线程上运行。它们只通过发布快照进行通信,而不是直接相互阻塞。
快照不应限于特定的帧速率。如果数据处理阶段停止,应该有多次在绘制或绘制同一批次之前批量更新。
我强烈建议对此问题采取被动方式。 RxJava为&#34;建议框&#34;提供了很好的示例。特征。它非常擅长正确处理像#34这样的要求;每次密钥更新都会在不同的线程上执行一个长时间运行的进程,并丢弃最后一个进程,如果有的话正在运行。或者,也许,不要在每次关键更新时都这样做,但等待50ms让用户在打字之前决定自己的想法#34;。
答案 2 :(得分:1)
在UI更新完成之前没有理由要停止处理的问题,难以回答。 (注意:runLater方法按收到的顺序执行UI更新)是否要防止向JavaFX线程发送许多Runnables的垃圾邮件?其他原因?
然而,您的基本想法适用于使用CountDownLatch,以便处理线程等待获取许可。如果您选择这种方法,请使用以下内容:
public class MyFXUtils {
public static runAndWait(final Runnable run) {
final CountDownLatch doneLatch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
public void run() {
try {
run.run();
} finally {
doneLatch.countDown();
}
}
});
doneLatch.await();
}
}
编辑:用CountDownLatch替换Semaphore