JavaFX 2:背景和 Platform.runLater 与任务/服务

Posted

技术标签:

【中文标题】JavaFX 2:背景和 Platform.runLater 与任务/服务【英文标题】:JavaFX 2: background and Platform.runLater vs Task/Service 【发布时间】:2014-10-03 03:54:15 【问题描述】:

我对 JavaFX 中的 Task/Service 的概念感到很困惑。

我在后台工作中使用了基于后台线程的模型,它调用Platform.runLater 来更新 UI。

假设我对进度条等不感兴趣。我正在对我的模型做一些实际工作,必须在 GUI 视图中更新(例如,根据后台某些连接随时间更新的参与者列表,基于某些用户输入的参与者列表,按年龄分类和起源)。这是我通常使用我启动的后台线程实现的,我在其中使用Platform.runLater

现在在 JavaFX 2 中,它们使用 Tasks 和 Services 来实现所有这些并发性,这表明最好使用它们。但我没有看到任何实现我所说的例子。

通过绑定一些属性来更新进度条很好(但这些是关于任务的信息而不是你的模型)。

那么,我如何才能真正根据我的模型更新视图的内容?我应该从Task 中拨打Platform.runLater 吗?如果不是,机制是什么?如何捕捉任务成功并获取结果(实际模型的更新)以更新视图?

不幸的是,Oracle 的教程在这方面不是很好。向我指出一些好的教程也会有所帮助。

【问题讨论】:

这个问题看起来很像:Platform.Runlater and Task Javafx 和 Javafx: Difference between javafx.concurent and Platform.runLater? 【参考方案1】:

TaskService 类旨在鼓励在 GUI 编程中的某些(但不是全部)常见场景中进行良好实践和正确使用并发。

一个典型的场景是应用程序需要执行一些逻辑来响应可能需要很长时间的用户操作(可能是长时间的计算,或者更常见的是数据库查找)。该过程将返回一个结果,然后用于更新 UI。如您所知,长时间运行的进程需要在后台线程上执行以保持 UI 响应,并且对 UI 的更新必须在 FX 应用程序线程上执行。

Task 类为此类功能提供了抽象,并表示执行并产生结果的“一次性”任务。 call() 方法将在后台线程上执行,旨在返回进程的结果,并且在 FX 应用程序线程上通知任务完成时的事件侦听器。强烈建议开发人员使用不可变状态初始化Task 实现,并让call() 方法返回一个不可变对象,从而保证后台线程和FX 应用程序线程之间的正确同步。

对这些类型的任务还有其他常见要求,例如随着任务的进行更新消息或进度。应用程序可能还需要监视类的生命周期状态(等待运行、正在运行、已完成、因异常而失败等)。正确地编程是相当困难的,因为它必然涉及在两个不同的线程中访问可变状态,并且有许多应用程序开发人员不知道其中的微妙之处。 Task 类为此类功能提供了简单的挂钩并负责所有同步。

要使用此功能,只需创建一个Task,其call() 方法会返回您的计算结果,注册一个处理程序,用于当状态从RUNNING 转换为SUCCEEDED,并在后台运行任务线程:

final Task<MyDataType> task = new Task<MyDataType>() 
    @Override
    public MyDataType call() throws Exception 
        // do work here...
        return result ;
    
;

task.setOnSucceeded(new EventHandler<WorkerStateEvent>() 
    @Override
    public void handle(WorkerStateEvent event) 
        MyDataType result = task.getValue(); // result of computation
        // update UI with result
    
);

Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();

这在幕后工作的方式是 Task 维护一个 state 属性,该属性是使用常规 JavaFX ObjectProperty 实现的。 Task 本身封装在 Callable 的私有实现中,Callable 实现是传递给超类构造函数的对象。因此,Callablecall() 方法实际上是在后台线程中执行的方法。 Callablecall()方法实现如下:

    在 FX 应用程序线程(即使用 Platform.runLater())上安排一个调用,更新 state,首先是 SCHEDULED,然后是 RUNNING 调用Taskcall()方法(即用户开发的call()方法) 在 FX 应用程序线程上安排一个调用,将value 属性更新为call() 方法的结果 在 FX 应用程序线程上安排一个调用,将state 属性更新为SUCCEEDED

这最后一步当然会调用使用state 属性注册的侦听器,并且由于状态更改是在 FX 应用程序线程上调用的,因此这些侦听器的 handle() 方法也将如此。

要全面了解其工作原理,请参阅source code。

通常,应用程序可能希望多次离散地执行这些任务,并监视代表所有进程的当前状态(即“正在运行”现在意味着一个实例正在运行等)。 Service 类只是通过 createTask() 方法为此提供了一个包装器。当Service 启动时,它通过调用createTask() 得到一个Task 实例,通过它的Executor 执行它,并相应地转换它自己的状态。

当然,有许多并发用例不适合(至少完全不适合)TaskService 实现。如果您有一个在整个应用程序期间运行的背景Thread(因此它代表一个连续的过程,而不是一次性任务),那么Task 类不适合。这方面的示例可能包括游戏循环或(可能)轮询。在这些情况下,您最好使用自己的 ThreadPlatform.runLater() 来更新 UI,但当然您必须处理两个线程都可以访问的任何变量的正确同步。根据我的经验,值得花一些时间考虑这些需求是否可以重新组织成适合TaskService 模型的东西,好像可以这样做,结果代码结构通常更清晰并且更易于管理。当然,在某些情况下情况并非如此,在这种情况下使用ThreadPlatform.runLater() 是合适的。

关于轮询(或定期安排的后台任务的任何其他要求)的最后一条评论。 Service 类看起来是一个很好的候选对象,但事实证明很难有效地管理周期性。 JavaFX 8 引入了一个ScheduledService 类,它很好地处理了这个功能,还增加了对后台任务重复失败等情况的处理。

【讨论】:

感谢您的详细解释。我对此有些熟悉,但这是一个很好的大纲,值得牢记在心。同时,我真正想知道的是,如果我稍后不运行平台,我如何将我的结果传达回 JAVAFX 线程。我在互联网上的某个地方看到了一些带有“setOnSucceeded”的例子,它需要一个处理程序。我不明白魔术,主线程完全调用该方法。这是文档中根本没有很好解释的事情。 “使用source,卢克”。我更新了幕后发生的事情的简要描述(基本上,Task 在必要的地方为您调用Platform.runLater(...))。请参阅 cmets 中链接的问题@jewelsea。

以上是关于JavaFX 2:背景和 Platform.runLater 与任务/服务的主要内容,如果未能解决你的问题,请参考以下文章

具有透明背景的 JavaFX 按钮

javafx如何设置文本框TextArea背景色为透明

javafx编程问题

JavaFX中的可复制Label / TextField / LabeledText

JavaFX 对背景的影响

如何在 JavaFX 中为组布局添加背景颜色?