【问题标题】:java thread immediately update UIjava线程立即更新UI
【发布时间】:2015-06-01 09:13:18
【问题描述】:

我有一个可视化计算几何算法的 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);
}

我希望你明白我的意思。我真的很好奇这个问题是否有更好的解决方案......

【问题讨论】:

  • 线程(算法)是否在你的程序中无限期地运行?
  • 是的,基本思想是,您可以附加不同的算法并运行它们。所以我对算法本身一无所知
  • 您的代码实际上不会编译,因为 lambda 表达式只能访问“有效最终”的局部变量。此外,guard 存在“活性”问题:实际上无法保证您的后台线程会在 UI 线程上看到对 guard 所做的更改。如果你想这样做,你应该使用AtomicBoolean,或者使用更高级别的并发API(见我的回答)。
  • 肯定它不会编译。我只是添加了这段代码来正式化我的想法

标签: java javafx


【解决方案1】:

简单的方法:阻塞后台线程直到更新完成:

您需要在 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();
    }
}

替代方法:确保在任何帧渲染期间不消耗超过一个更新,如果更新未决,则阻塞后台线程

这里是一个有些不同的方法。创建一个容量为1BlockingQueue 来保存更新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();

这基本上确保了在任何时候都不会安排超过一个更新,后台线程会阻塞,直到任何待处理的更新被消耗。动画计时器检查(不阻塞)每个帧渲染的未决更新,确保执行每个更新。这种方法的好处是您可以增加阻塞队列的大小,有效地保留待处理更新的缓冲区,同时仍然确保在任何单帧渲染期间不会消耗超过一个更新。如果偶尔的计算需要比其他时间更长的时间,这可能会很有用;它使这些计算有机会在其他计算等待执行时进行计算。

【讨论】:

  • 我真的很喜欢使用 ActionTimer 的方法。但是关于底层 BlockedQueue 的一个问题。当我轮询runnable时,block是立即释放还是在runnable执行后释放?
  • 退出poll()方法后块会被释放,所以后台线程将能够开始下一次计算。当然,直到下一帧(下一次调用handle(...))之前,您不会再处理任何计算结果。
  • 如果你只希望它在执行更新后释放块(虽然我看不出你想要这个的正当理由),然后用对@的调用替换对poll()的调用987654342@,并在update.run() 之后立即调用poll()(丢弃结果)。
  • 你是对的,没有理由这样做......只是我不再想到,计算将在下一次更新 UI 调用时被阻止
  • 对不起,我的 javafx 主线程中出现了 java.lang.nullpointerException
【解决方案2】:

如果没有在 UI 更新完成之前停止处理的原因,这个问题很难回答。 (注意:runLater 方法按收到的顺序执行 UI 更新)是为了防止向 JavaFX 线程发送大量 Runnable 垃圾邮件吗?其他原因?

但是,您的基本想法是使用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 替换信号量

【讨论】:

  • 我可能错了,但是如果后台线程在可运行之前获取信号量,则不会等待。我认为这是可能的(甚至可能)。
【解决方案3】:

编辑:

所以,我总是在原型中做到这一点的最快方法如下,转换:

//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 形状实例与您的算法处理的形状分开。我会通过使用不同的类类型并在两者之间进行同步来做到这一点。

图形算法往往比可视化算法快得多。如果算法在小数据集上运行,那么渲染通常会显着减慢它。通过运行相同的算法(有或没有可视化)可以很容易地看到它。

如果数据集比最琐碎的数据集大,那么单帧的绘制很容易花费超过一秒钟的时间。预计交互式可视化将“实时”响应,最好每秒响应多次。

数据可视化工具有很多方法可以解决这个问题。最佳候选人总是包括:

  1. 简化可视化。绘制更简单的形状而不是复杂的形状,例如从框中删除舍入。 LOD(详细程度)也适用于这一点:在交互式滚动期间,可视化元素可能会被边界框对应物替换。

  2. 选择性隐藏。仅绘制整个数据集的一部分。

  3. 并行化和硬件加速。 GPU 本身提供了许多方法来处理复杂的可视化。通常,低级编程 API(OpenGL、着色器程序)比所有高级封装 API(包括 JavaFX)具有更好的吞吐量

大多数情况下,最终解决方案不仅包含上述几点,还包含其他方面,包括特定领域的优化。

可视化工具总是有很多限制,比如最常见的一个:必须在专用线程中更新(线程限制方法)。它们还带有可视化特定的数据结构。

从数据处理算法阶段,最常见的要求之一就是不能被可视化阻塞或延迟。算法也是以一种风格编写的,这种风格不能很好地转化为可视化手段:数据结构上的命令式循环而不是更新可观察的可绘制对象。这样做有一个很好的理由:预计算法会针对性能或内存消耗进行优化。

解决该问题的一种架构方法可能如下:

  1. 数据处理阶段在预定义的点生成快照。添加、修改和删除操作都作为这个包发布。它可以是正在处理的数据结构的副本,也可以是合并事件的形式。

  2. 数据处理和数据可视化运行在不同的线程上。它们仅通过发布快照的方式进行通信,从不直接相互阻止。

  3. 不应将快照限制为特定的帧速率。如果数据处理阶段停止,应该有在绘制之前批量更新或多次绘制同一批次的方法。

我强烈建议对问题采取反应式方法。 RxJava 为“建议框”功能提供了很好的示例。它非常擅长正确处理要求,例如“每次密钥更新都会在不同的线程上执行一个长时间运行的进程,并丢弃最后一个正在运行的进程。或者,不要在每次密钥更新时都这样做,而是等待用户 50 毫秒在他结束打字之前做出决定”。

【讨论】:

  • 性能在这里不是问题。当然,我会通过执行 UI 更新来大幅增加某个算法的运行时间。但由于这个项目主要是为了“逐步”演示算法的工作,所以没关系。所以一个清晰的 UI 更新是关键的愿望
  • 好吧,那么其他方法可能会更好:直接在可观察对象上实现算法,同步更新绘图。
  • 我正在添加一个编辑,我如何在 Swing 中构建这样的原型
  • 听起来不错,非常感谢。关于我在问题中的初步方法:有人看到它有什么问题吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
相关资源
最近更新 更多