在
Task和
Service课程旨在鼓励良好做法,在GUI编程的一些(但不是全部)常见的场景正确使用并发。
典型的情况是,应用程序需要执行一些逻辑来响应可能需要很长时间(可能是很长的计算,或更常见的是数据库查找)的用户操作。该过程将返回结果,然后将其用于更新UI。如您所知,长时间运行的进程需要在后台线程上执行以保持UI响应,并且UI的更新必须在FX
Application Thread上执行。
的
Task类提供这种功能的抽象,并且表示被执行,并产生一个结果的“一次性”的任务。该
call()方法将在后台线程上执行,并设计为返回进程的结果,并且在FX
Application线程上会通知事件完成时的事件侦听器。强烈建议开发人员
Task使用不可变状态初始化实现,并让该
call()方法返回不可变对象,以确保后台线程和FX
Application Thread之间的正确同步。
这些类型的任务还有其他共同要求,例如,随着任务的进展更新消息或进度。应用程序可能还需要监视类的生命周期状态(等待运行,正在运行,已完成,因异常而失败等)。正确编程非常困难,因为它必然涉及在两个不同线程中访问可变状态,并且许多应用程序开发人员都不知道其细微之处。本
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 shutdownt.start();它在幕后的工作方式是
Task维护一个
state属性,该属性是使用常规JavaFX实现的
ObjectProperty。在
Task本身被包裹在一个私人的实施
Callable和
Callable执行是传递给父类的构造对象。因此,
Callable的
call()方法实际上是在后台线程中执行的方法。所述
Callable的
call()方法是这样实现的:
- 安排FX Application线程上的调用(即使用
Platform.runLater()
),state
以将SCHEDULED
,更新为,然后更新为RUNNING
- 调用的
call()
方法Task
(即用户开发的call()
方法) - 安排对FX Application Thread的调用,以将
value
属性更新为call()
方法的结果 - 安排对FX Application Thread的调用,以将该
state
属性更新为SUCCEEDED
当然,这最后一步将调用在
state属性中注册的侦听器,并且由于状态更改是在FX Application
Thread上调用的,因此这些侦听器的
handle()方法也将被调用。
要全面了解其工作原理,请参见源代码。
通常,应用程序可能需要多次执行这些任务,并监视代表所有进程的当前状态(即“正在运行”现在意味着一个实例正在运行,等等)。的
Service类简单地通过为此提供了一个包装
createTask()方法。当
Service启动时,它
Task通过调用获取实例,通过实例
createTask()执行该实例
Executor,并相应地转换其自身的状态。
当然,有很多并发用例不适合(至少是完全不适合)
Task或
Service实现。如果您有一个
Thread在整个应用程序运行期间都在运行的背景(因此它表示一个连续的过程,而不是一次性的任务),则
Task该类不是一个很好的选择。例如,游戏循环或(可能)轮询。在这些情况下,最好使用自己的
Threadwith
Platform.runLater()来更新UI,但当然,您必须处理两个线程都可以访问的任何变量的正确同步。以我的经验,值得花些时间考虑这些需求是否可以重新组织成适合于
Task或的东西。
Service模型,好像可以做到这一点一样,生成的代码结构通常更简洁,更易于管理。当然,在某些情况下并非如此,但在这种情况下使用
Thread和
Platform.runLater()是合适的。
关于轮询的最后一项评论(或对定期计划的后台任务的其他任何要求)。这个
Service班级看起来像是一个很好的候选人,但是事实证明要有效地管理周期性非常困难。JavaFX
8引入了一个
ScheduledService很好地处理此功能的类,并且还增加了对诸如后台任务反复失败等情况的处理。



