JavaFX 2:后台和Platform.runLater与任务/服务

时间:2014-08-10 00:12:32

标签: java javafx concurrency javafx-2

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

我已经使用基于后台线程的模型进行后台工作,对于UI的任何更新都会调用Platform.runLater

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

现在,在JavaFX 2中,他们使用TaskService来获得所有这些并发性,这表明使用它们会更好。但我没有看到任何实现我所谈论的例子。

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

那么,我如何根据我的模型实际更新我的观点内容?我应该从Platform.runLater内拨打Task吗?如果没有,机制是什么?如何在任务成功时捕获并获得结果(更新实际模型)以更新视图?

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

1 个答案:

答案 0 :(得分:16)

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

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

Task类提供了这种功能的抽象,并代表了一次性"一次性"执行的任务并生成结果。 call()方法将在后台线程上执行,旨在返回进程的结果,并且在FX Application线程上通知任务完成时有事件侦听器。强烈建议开发人员使用不可变状态初始化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实现是传递给超类构造函数的对象。因此,Callable的{​​{1}}方法实际上是在后台线程中执行的方法。 call()的{​​{1}}方法实现如下:

  1. 在FX应用程序线程上安排一个呼叫(即使用Callable),更新call(),首先更新为Platform.runLater(),然后更新为state
  2. 调用SCHEDULED的{​​{1}}方法(即用户开发的RUNNING方法)
  3. 在FX应用程序线程上安排一个调用,将call()属性更新为Task方法的结果
  4. 在FX应用程序主题上安排一次调用,将call()属性更新为value
  5. 最后一步当然会调用在call()属性中注册的侦听器,并且因为在FX应用程序线程上调用了状态更改,所以这些侦听器将会被调用。 state方法。

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

    通常,应用程序可能希望多次离散执行这些任务,并监视代表所有进程的当前状态(即&#34;运行&#34;现在意味着一个实例正在运行,等等)。 SUCCEEDED类只是通过state方法为此提供包装器。启动handle()后,通过调用Service获取createTask()实例,通过其Service执行,并相应地转换其自身状态。

    当然,许多并发用例不适合(至少干净地)适用于TaskcreateTask()实现。如果您有一个在应用程序的整个持续时间内运行的后台Executor(因此它代表一个连续的进程,而不是一次性任务),那么Task类不是一个好的适合。这方面的例子可能包括游戏循环或(可能)轮询。在这些情况下,您可能最好使用自己的ServiceThread来更新UI,但当然您必须处理两个线程可能访问的任何变量的正确同步。根据我的经验,值得花一些时间思考是否可以将这些需求重新组织成适合TaskThread模型的内容,就好像这可以通过生成的代码结构来完成通常更清洁,更容易管理。当然,情况并非如此,在这种情况下,使用Platform.runLater()Task是合适的。

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