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

Maa*_*mon 3 java concurrency javafx javafx-2

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

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

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

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

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

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

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

Jam*_*s_D 16

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

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

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

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

要使用此功能,只需要创建一个Task它的call()方法返回的计算结果,登记时从状态转换的处理程序RUNNINGSUCCEEDED,并运行在后台线程任务:

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();
Run Code Online (Sandbox Code Playgroud)

这种方式在幕后工作的方式是Task维护一个state属性,这是使用常规JavaFX实现的ObjectProperty.它Task本身包含在私有实现中Callable,而Callable实现是传递给超类构造函数的对象.因此,Callablecall()方法实际上是在后台线程中执行的方法.所述Callablecall()方法是这样实现的:

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

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

要全面了解其工作原理,请参阅源代码.

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

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

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