java线程立即更新UI
java thread immediately update UI
我有一个可视化计算几何算法的 javaFX 应用程序。算法的执行发生在另一个线程中,我们称之为 mainComputingThread
。
算法可以随时更新 UI 个 adding/removing/modifying 个形状。
所以代码看起来像:
//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);
}
我希望你明白我的意思。我真的很好奇这个问题是否有更好的解决方案...
编辑:
所以,我总是在原型中做的最快的方法如下,转换:
//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 形状实例与您的算法处理的形状分开。我会通过使用不同的 class 类型并在两者之间进行同步来做到这一点。
图形算法往往比可视化算法快得多。如果算法 运行s 在小数据集上运行,那么渲染通常会显着减慢它的速度。 运行在有和没有可视化的情况下使用相同的算法可以很容易地看到它。
如果数据集比最普通的数据集大,那么单帧的绘制很容易花费超过一秒的时间。交互式可视化预计会在 "real time" 内响应,最好是每秒多次。
数据可视化工具有很多方法可以解决这个问题。最佳候选人总是包括:
简化可视化。绘制更简单的形状而不是复杂的形状,例如从盒子中移除圆角。 LOD(细节级别)也适用于这一点:在交互式滚动期间,可视化元素可能会被对应的边界框替换。
选择性隐藏。仅绘制整个数据集的一部分。
并行化和硬件加速。 GPU 本身就提供了许多处理复杂可视化的方法。通常,低级编程 APIs(OpenGL、着色器程序)比每个高级包装 API 允许更好的吞吐量,包括 JavaFX
大多数情况下,最终解决方案不仅包含以上几点,还包含其他方面,包括特定领域的优化。
可视化工具总是有很多限制,比如最常见的一个:必须在专用线程中更新(线程限制方法)。它们还带有特定于可视化的数据结构。
从数据处理算法阶段来说,最常见的一个需求就是不能被可视化阻塞或者延迟。这些算法也以一种风格编写,这种风格不能很好地转化为可视化方式:命令式循环数据结构而不是更新可观察的可绘制对象。不过,这是有充分理由的:算法应该针对性能或内存消耗进行优化。
解决该问题的一种架构方法可能如下所示:
数据处理阶段在预定义点生成 快照。添加、修改和删除操作都发布为这个数据包。它可以只是正在处理的数据结构的副本,也可以是合并事件的形式。
不同线程上的数据处理和数据可视化运行。他们只通过发布快照的方式进行通信,从不直接阻止对方。
快照不应受限于特定的帧率。如果数据处理阶段停止,应该有在绘制或多次绘制同一批次之前批量更新的方法。
我强烈建议采用反应式方法解决问题。 RxJava 为 "suggestion box" 功能提供了很好的示例。它非常擅长正确处理 "Every key update do a long running process on different thread and discard the last one, if any was running. Or maybe, don't do it on every key update, but wait 50ms for the user to make up his mind before he ends typing".
这样的要求
很难回答这个问题,而且没有理由要在 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();
}
}
编辑:将 Semaphore 替换为 CountDownLatch
简单方法:阻塞后台线程直到更新完成:
您需要更新 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 的 Runnable
。从您的后台线程,将 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();
这基本上确保在任何时候都不会安排超过一个更新,后台线程会阻塞,直到消耗完所有待处理的更新。动画计时器检查(不阻塞)每个帧渲染上的未决更新,确保执行每个更新。这种方法的好处是您可以增加阻塞队列的大小,有效地保留待处理更新的缓冲区,同时仍然确保在任何单帧渲染期间不会消耗超过一个更新。如果偶尔有比其他计算花费更长的计算时间,这可能会有用;它使这些计算有机会在其他计算等待执行时进行计算。
我有一个可视化计算几何算法的 javaFX 应用程序。算法的执行发生在另一个线程中,我们称之为 mainComputingThread
。
算法可以随时更新 UI 个 adding/removing/modifying 个形状。
所以代码看起来像:
//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);
}
我希望你明白我的意思。我真的很好奇这个问题是否有更好的解决方案...
编辑:
所以,我总是在原型中做的最快的方法如下,转换:
//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 形状实例与您的算法处理的形状分开。我会通过使用不同的 class 类型并在两者之间进行同步来做到这一点。
图形算法往往比可视化算法快得多。如果算法 运行s 在小数据集上运行,那么渲染通常会显着减慢它的速度。 运行在有和没有可视化的情况下使用相同的算法可以很容易地看到它。
如果数据集比最普通的数据集大,那么单帧的绘制很容易花费超过一秒的时间。交互式可视化预计会在 "real time" 内响应,最好是每秒多次。
数据可视化工具有很多方法可以解决这个问题。最佳候选人总是包括:
简化可视化。绘制更简单的形状而不是复杂的形状,例如从盒子中移除圆角。 LOD(细节级别)也适用于这一点:在交互式滚动期间,可视化元素可能会被对应的边界框替换。
选择性隐藏。仅绘制整个数据集的一部分。
并行化和硬件加速。 GPU 本身就提供了许多处理复杂可视化的方法。通常,低级编程 APIs(OpenGL、着色器程序)比每个高级包装 API 允许更好的吞吐量,包括 JavaFX
大多数情况下,最终解决方案不仅包含以上几点,还包含其他方面,包括特定领域的优化。
可视化工具总是有很多限制,比如最常见的一个:必须在专用线程中更新(线程限制方法)。它们还带有特定于可视化的数据结构。
从数据处理算法阶段来说,最常见的一个需求就是不能被可视化阻塞或者延迟。这些算法也以一种风格编写,这种风格不能很好地转化为可视化方式:命令式循环数据结构而不是更新可观察的可绘制对象。不过,这是有充分理由的:算法应该针对性能或内存消耗进行优化。
解决该问题的一种架构方法可能如下所示:
数据处理阶段在预定义点生成 快照。添加、修改和删除操作都发布为这个数据包。它可以只是正在处理的数据结构的副本,也可以是合并事件的形式。
不同线程上的数据处理和数据可视化运行。他们只通过发布快照的方式进行通信,从不直接阻止对方。
快照不应受限于特定的帧率。如果数据处理阶段停止,应该有在绘制或多次绘制同一批次之前批量更新的方法。
我强烈建议采用反应式方法解决问题。 RxJava 为 "suggestion box" 功能提供了很好的示例。它非常擅长正确处理 "Every key update do a long running process on different thread and discard the last one, if any was running. Or maybe, don't do it on every key update, but wait 50ms for the user to make up his mind before he ends typing".
这样的要求很难回答这个问题,而且没有理由要在 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();
}
}
编辑:将 Semaphore 替换为 CountDownLatch
简单方法:阻塞后台线程直到更新完成:
您需要更新 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 的 Runnable
。从您的后台线程,将 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();
这基本上确保在任何时候都不会安排超过一个更新,后台线程会阻塞,直到消耗完所有待处理的更新。动画计时器检查(不阻塞)每个帧渲染上的未决更新,确保执行每个更新。这种方法的好处是您可以增加阻塞队列的大小,有效地保留待处理更新的缓冲区,同时仍然确保在任何单帧渲染期间不会消耗超过一个更新。如果偶尔有比其他计算花费更长的计算时间,这可能会有用;它使这些计算有机会在其他计算等待执行时进行计算。