JavaFX中的Platform.runLater和Task

103

我一直在研究这个问题,但我仍然非常困惑。

有人能给我一个具体的例子,说明何时使用 Task 以及何时使用 Platform.runLater(Runnable);?它们到底有什么区别?何时使用其中任何一个有黄金法则吗?

并且,如果我理解不正确,请纠正我,这两个“对象”不是一种在 GUI 中创建另一个线程(用于更新 GUI)的方法吗?

4个回答

126

对于快速简单的操作,请使用Platform.runLater(...) ,对于复杂和大型操作,请使用Task

示例:为什么不能使用Platform.runLater(...)进行长时间的计算(从以下引用中获取)。

问题:后台线程只是从0计数到100万,并在UI中更新进度条。

使用Platform.runLater(...)的代码:

final ProgressBar bar = new ProgressBar();
new Thread(new Runnable() {
    @Override public void run() {
    for (int i = 1; i <= 1000000; i++) {
        final int counter = i;
        Platform.runLater(new Runnable() {
            @Override public void run() {
                bar.setProgress(counter / 1000000.0);
            }
        });
    }
}).start();

这是一段可怕的代码,违背了自然法则(以及一般编程规范)。首先,看到这个双重嵌套的Runnables,你会失去大脑细胞。其次,它将用许多小的Runnables淹没事件队列——事实上有一百万个。显而易见,我们需要一些API来使编写后台工作人员并与UI进行通信更容易。

使用任务的代码:

Task task = new Task<Void>() {
    @Override public Void call() {
        static final int max = 1000000;
        for (int i = 1; i <= max; i++) {
            updateProgress(i, max);
        }
        return null;
    }
};

ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();

它没有前面代码所展示的任何缺陷

参考资料: JavaFX 2.0中的工作线程


这是正确的链接(http://download.oracle.com/otndocs/products/javafx/2/samples/Ensemble/index.html#SAMPLES/Concurrency/Task),但我无法编辑它,因为编辑只有两个字符。 - Aerus
39
说一个适用于“快速”操作,另一个适用于“复杂”操作有点误导。毕竟,主要的区别是其中一个允许从其他地方运行代码在JFX GUI线程中,而另一个则完全相反——允许从GUI线程中在后台线程中运行代码(额外添加了能够在此过程中与GUI线程通信的优点)。 - Sergei Tachenov
我想保存每个场景的图像 - 这需要一些时间。如果通过Task在单独的线程上运行,会引起问题吗?我知道所有与GUI相关的工作都必须在FxApplication线程上完成。 - WestCoastProjects
@Sergey-Tachenov 所以你是从任务线程中使用runLater()来更新GUI线程,这种情况下你想要做更多的事情而不仅仅是更新单个属性,比如进度? - simpleuser
你不再需要像这样声明Runnables了。你可以使用lambda语法:() -> { // Code here },或者() -> runSingleThing();。因此,上面的代码会大大压缩,并且看起来不那么累赘。答案仍然是完全有效的 :) - ManoDestra
显示剩余3条评论

69
  • Platform.runLater:如果您需要从非GUI线程更新GUI组件,可以使用它将您的更新放入队列中,然后由GUI线程尽快处理。
  • Task 实现了 Worker 接口,用于在GUI线程之外运行长时间任务(以避免冻结应用程序),但仍需要在某个阶段与GUI交互。

如果您熟悉 Swing,则前者相当于 SwingUtilities.invokeLater,后者则相当于 SwingWorker 的概念。

Task 的 javadoc 给出了许多示例,这些示例应该可以说明如何使用它们。您还可以参考并发教程


谢谢。你能给一个使用平台的小例子吗?它可以在GUI线程之外使用吗?文档中的任务示例真的不太清楚。 - Marc Rasmussen
是的,Platform.runLater可以在GUI线程之外使用 - 这是它的主要目的。您可能会发现这个关于任务的教程比javadoc更有信息量。 - assylias
3
您可以这样使用Platform.runLater:Platform.runLater(new Runnable() {public void run() {updateYourGuiHere();}});,其中updateYourGuiHere()是用于更新您的图形用户界面的方法。 - assylias
我将如何能够使用GUI组件呢 = :S - Marc Rasmussen
1
@KevinMeredith:Task 的 call 方法不应该在 FX 线程上调用,但 Task 提供了桥接方法(如 updateProgress 等),可以在 FX 线程上运行。请参阅 javadoc 和教程以获取更多信息。 - assylias
显示剩余4条评论

14

现在它可以更改为lambda版本

@Override
public void actionPerformed(ActionEvent e) {
    Platform.runLater(() -> {
        try {
            //an event with a button maybe
            System.out.println("button is clicked");
        } catch (IOException | COSVisitorException ex) {
            Exceptions.printStackTrace(ex);
        }
    });
}

8
如果您正在处理GUI事件,那么您就在GUI线程中。那么为什么会使用runLater()呢? - TFuto
虽然你说得没错,但Caglar的回答试图强调使用lambda表达式,而不一定是提供一个好的编码示例。我使用Platform.runLater(() -> progressBar.setProgress(X/Y)); - Taelsin

3

使用明确的Platform.runLater()的一个原因可能是,您将UI中的属性绑定到了服务(result)属性。因此,如果您要更新绑定的服务属性,必须通过runLater()来完成。

在UI线程中,也称为JavaFX应用程序线程:

...    
listView.itemsProperty().bind(myListService.resultProperty());
...

在服务实现(后台工作者)中:

...
Platform.runLater(() -> result.add("Element " + finalI));
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接