我对Task/Service在JavaFX中的概念非常困惑。
我在后台工作中使用了一个基于后台线程的模型,它调用Platform.runLater来更新UI。
假设我对进度条什么的不感兴趣。我正在对我的模型做一些必须在GUI视图中更新的实际工作(例如,根据背景中的某种连接随时间更新的参与者列表,基于某些用户输入的参与者列表,按年龄和来源分类)。这就是我通常使用启动的后台线程实现的,并且在其中使用Platform.runLater。
现在,在JavaFX 2中,它们使用Tasks和Services实现了所有这些并发性,这表明使用它们更好。但我看不出有什么例子能达到我所说的目的。
通过绑定一些属性来更新进度条很好(但这些是关于任务的信息,而不是您的模型)。
那么,我如何能够根据我的模型实际更新我的视图的内容呢?我应该从Platform.runLater内部打电话给Task吗?若否,机制是甚麽?当任务成功时如何捕获并获得结果(实际模型的更新)来更新视图?
不幸的是,Oracle的教程在这方面不是很好。给我指点一些好的教程也会有帮助。
发布于 2014-08-10 02:19:38
Task和Service类的设计是为了鼓励在GUI编程中某些(但不是所有)常见场景中的良好实践和正确使用并发性。
一个典型的场景是,应用程序需要执行一些逻辑来响应可能需要很长时间的用户操作(可能是长时间的计算,或者更常见的是数据库查找)。该过程将返回一个结果,然后用于更新UI。如您所知,长时间运行的进程需要在后台线程上执行,以保持UI响应,而对UI的更新必须在FX应用程序线程上执行。
Task类为这种功能提供了一个抽象,并表示一个执行并产生结果的“一次性”任务。call()方法将在后台线程上执行,并被设计为返回进程的结果,并且在FX线程上通知任务完成时会有事件侦听器。强烈鼓励开发人员以不可变状态初始化Task实现,并让call()方法返回一个不可变对象,这将确保后台线程和之间的适当同步。
对于这些类型的任务,还有其他常见的要求,例如,随着任务的进展更新消息或进度。应用程序还可能需要监视类的生命周期状态(等待运行、运行、完成、异常失败等)。正确地编程是相当困难的,因为它必然涉及访问两个不同线程中的可变状态,并且有许多应用程序开发人员不知道这些微妙之处。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的call()方法实际上是在后台线程中执行的方法。Callable的call()方法实现如下:
Platform.runLater())的调用,该线程首先将state更新为SCHEDULED,然后更新为RUNNINGTask的Task方法(即用户开发的call()方法)value属性更新为call()方法的结果state属性更新为SUCCEEDED当然,最后一步将调用通过state属性注册的侦听器,并且由于状态更改是在上调用的,所以这些监听器的handle()方法也会调用。
要充分理解这是如何工作的,请参阅source code。
通常,应用程序可能希望多次执行这些任务,并监视表示所有进程的当前状态(即“运行”现在意味着一个实例正在运行,等等)。Service类只是通过一个createTask()方法提供了一个包装器。当Service启动时,它通过调用createTask()获得一个Task实例,通过它的Executor执行它,并相应地转换它自己的状态。
当然,有许多并发用例不适合(至少是干净的) Task或Service实现。如果您有一个后台Thread在整个应用程序期间运行(因此它代表一个连续的过程,而不是一个一次性的任务),那么Task类就不是很适合的了。这方面的例子可能包括一个游戏循环,或者(可能)轮询。在这些情况下,最好使用自己的Thread和Platform.runLater()来更新UI,但当然,您必须处理两个线程可能访问的任何变量的适当同步。根据我的经验,值得花一些时间考虑这些需求是否可以重新组织成适合于Task或Service模型的东西,就好像可以这样做一样--结果代码结构通常更干净,更易于管理。当然,在某些情况下,情况并非如此,在这种情况下,使用Thread和Platform.runLater()是合适的。
关于轮询的最后一个评论(或定期安排的背景任务的任何其他要求)。Service类看起来是一个很好的候选类,但是很难有效地管理周期性。JavaFX 8引入了一个ScheduledService类,它很好地处理了这个功能,还增加了对后台任务重复失败等情况的处理。
https://stackoverflow.com/questions/25224323
复制相似问题