DOC-13-01 JavaFX并发

本章描述使用javafx.concurrent包提供的功能来创建多线程的应用程序。

你将学会如何在后台委托耗时的任务执行,来保持你的JavaFX应用用户界面(UI)是可响应的。

为什么要使用javafx.concurrent包?

代表了JavaFX应用程序图形用户界面的场景图并不是线程安全的,而且仅可以通过UI线程亦被称为JavaFX应用程序线程访问和修改。在JavaFX应用程序线程中执行长时间运行的任务,不可避免会造成应用程序的UI无响应。最好的实现是在一个或多个后台线程上处理这些任务,并且让JavaFX应用程序线程来处理用户事件。

如果你有特别的需求或需要额外的能力,通过创建一个Runnable对象和新的线程来实现一个后台工作是一个比较合适的方法。注意在某些情况下你必须与JavaFX应用程序进程保持通信,可能是后台任务执行的结果或进展。

对于大多数情况以及对大多数开发者来说,都推荐使用javafx.concurrent包提供的JavaFX API,它可以处理好多线程代码和UI的交互,并且可以保证这个交互发生在正确的线程上。

概览javafx.concurrent包

Java平台通过java.util.concurrent包提供了一个完整可用的并发库。javafx.concurrent包考虑到JavaFX应用程序线程和其他面向GUI开发者的限制条件,促使了现有API改变。

javafx.concurrent包由Worker接口和两个具体的实现Task和Service类组成。Worker接口提供了对后台工作与UI之间通信有用的API。Task类是java.util.concurrent.FutureTask类的一个完整的可观察的实现。Task类使开发者可以在JavaFX应用程序中实现异步任务。Service类执行这些任务。

WorkerStateEvent类指定了一个工作实现状态发生变化时的事件。Task和Service类都实现了EventTarget接口,因此可以支持监听这个状态事件。

Worker接口

Worker接口定义了一个执行一个或多个后台线程工作的对象,Worker对象的状态在JavaFX应用程序线程中是可观察且可用的。

Worker对象的生命周期定义如下。创建时,Worker对象是READY状态。开始计划工作时,Worker对象转变为SCHEDULED状态。之后当Worker对象执行工作时,状态变为RUNNING。注意当Worker对象没有计划而直接开始时,状态也是先转变为SCHEDULED再转为RUNNING状态,并且value属性也设置到Worker对象的结果中。如果任何异常在Worker对象执行过程中抛出,状态变为FAILED并且exception属性也会设置为产生的异常类型。在Worker对象结束前的任何时候,开发者都可以调用cancel方法来终止,此时状态将变为CANCELLED。

ScheduledService对象的生命周期的一些区别可以在ScheduledService类这一节中找到。

Worker对象的工作完成过程可以通过三种不同的属性获取,totalWork,workDone和progress。

可以在API文档中了解更多参数值范围的信息。

Task类

任务是用来实现需要在后台完成的工作的逻辑。首先你需要扩展Task类。实现Task的类必须重写call方法来处理后台工作和返回结果。

call方法在后台进程中被调用,因此这个方法只能操作后台线程读写安全的状态。例如,通过call方法操作一个活动的场景图会抛出运行时异常。另一方面,Task类就是设计用来和JavaFX GUI应用程序一起使用的,它能确保公有属性、错误或取消的变更通知、事件处理器和状态的修改在JavaFX应用程序线程执行。在call方法内部,你可以使用updateProgress,updateMessage,updateTitle方法,这些方法可以在JavaFX应用程序线程更新对应属性的值。但如果任务被取消时,call方法中的返回值会被忽略掉。

注意Task类兼容Java并发库是因为它继承java.utils.concurrent.FutureTask类,此类实现了Runnable接口。因此Task对象可以在Java并发Executor API中使用,也可以作为参数传递给线程。你可以使用FutureTask.run()方法来直接调用Task对象,这样可以在另一个后台线程执行该任务。如果比较了解Java并发API,可以帮助你了解JavaFX的并发。

一个任务可以由以下几种方法开始:

● 通过启动一个以指定任务作为参数的线程

● 通过使用ExecutorService API

Task类定义了一个一次性且不能重用的对象。如果你需要一个可以重用的Worker对象,使用Service类。

取消任务

Java中没有可靠的方法来停止一个在运行中的线程。但是当在任务中调用cancel方法时,任务必须停止运行。任务假设在工作中周期性检查是否已被取消,可以在call方法中使用isCancelled方法检查。例1-1展示了一个正确的Task类的实现来检查取消。

例1-1

如果任务实现有阻塞调用,如Thread.sleep,并且在阻塞调用中任务被取消,将会抛出一个InterruptedException。对于这些任务来说,一个中断线程可能是一个取消的任务的信号。因此任务有阻塞调用时需要双重检测isCancelled方法来确保InterruptedException是因为任务取消而抛出,如例1-2。

例1-2

展示后台任务的进度

多线程应用的典型用例是展示后台任务的进度。假设你有一个从一数到一百万的后台任务和一个进度条,你必须随着后台进度而更新进度条的进度。例1-3展示了如何更新一个进度条。

例1-3

首先在实现工作逻辑的代码中重写call方法创建任务并调用updateProgress方法,这将需要更新任务的progress,totalWork和workDone属性。这很重要,因为你现在可以使用ProgressProperty方法来获得任务的进度,并将任务的进度绑定到任务条上。

Service类

Service类是设计来在一个或多个后台线程上执行一个Task对象。Service类的方法和状态必须只可以由JavaFX应用程序线程访问。此类的目的是为了帮助开发者实现后台线程与JavaFX应用程序线程之间的正确交互。

对于Service对象有如下控制:你可以按需启动,取消和重启服务。使用Service.start()方法来启动Service对象。

使用Service类,你可以观察后台工作的状态也可以选择取消它。其次你可以重置服务并重启它。因此服务可以声明式定义,也可以按需要重启。

查看ScheduledService类这一节,可以了解一个需要自动重启的服务。

当实现Service类的子类时,确保向Task对象暴露作为子类属性的输入参数。

服务可以按以下其中一种执行:

● 可以被一个特定服务指定的Executor对象执行。

● 如果没有执行器被指定,则由守护进程执行。

● 由自定义的执行器执行,如ThreadPoolExecutor。

例1-4展示了一个Service类的实现,其中读取了任何一个URL的第一行并将其作为字符串返回。

例1-4

WorkerStateEvent类和状态转换

每当Worker实现的状态改变时,由WorkerStateEvent类定义的一个适当的事件将会发生。例如当Task对象转变为SUCCEEDED状态时,WORKER_STATE_SUCCEEDED事件将会发生,onSucceeded事件控制器将被调用,之后受保护的便利的succeed方法将被在JavaFX应用程序线程调用。

受保护的便利的方法有几种,如cancelled,failed,running,scheduled和succeeded,当Worker实现转换到对应状态时将会被调用。当状态改变时这些方法可以被Task和Service类的子类重写,来实现应用程序中的逻辑。例1-5中展示了一个Task实现,其中在任务成功,取消和失败的情况下更新了状态信息。

例1-5

ScheduledService

涉及到调用轮询的许多情况下都需要一个自动重启的服务。为了满足需求,Service类被扩展出了ScheduledService类。ScheduledService类代表了一个在成功执行后,或者在特定情况下失败后,可以自动重启的服务。

当ScheduledService对象被创建时,即为READY状态。

在调用ScheduledService.start()或者ScheduledService.restart()方法后,ScheduledService对象在delay属性定义的范围内变为SCHEDULED状态。

在处于RUNNING状态时,ScheduledService执行其任务。

任务成功完成

在任务完成后,ScheduledService对象转变为SUCCEEDED状态,然后变为READY状态,然后再变为SCHEDULED状态。再次变为SCHEDULED状态的周期取决于最后一次转变为RUNNING状态的时间、现在的时间、和定义相邻两次运行时间的最小值的period属性值。如果前一次运行结束早于周期失效,ScheduledService对象会保持在SCHEDULED状态,直到周期失效。否则如果前一次执行比指定的周期更长,ScheduledService对象会立刻转变为RUNNING状态。

任务失败

当任务以FAIL状态结束的情况下,ScheduledService对象会重启或者退出,这取决于restartOnFailure,backoffStrategy和maximumFailureCount属性的值。

如果restartOnFailure属性为false,ScheduledService对象会转变为FAILED状态并退出。在这种情况下,你可以手动重启这个已失败的ScheduledService对象。

如果restartOnFailure属性是true,ScheduledService对象会转变为SCHEDULED状态,并在cumulativePeriod属性定义的周期内保持此状态,cumulativePeriod属性可以通过调用backoffStrategy属性获取到结果。使用cumulativePeriod属性,你可以强制已失败的ScheduledService在下一次运行前等待更长。在ScheduledService成功完成后,cumulativePeriod属性会被重置为period属性的值。当失败数量达到maximumFailureCount属性的值时,ScheduledService对象会转变为FAIL状态然后退出。

在ScheduledService对象运行时,delay和period属性发生任何变化都会在下一次循环中生效。delay和period属性的默认值为0。

结论

在这一章中,你学到了javafx.concurrent包提供的基本功能,并且熟悉了Task和Service类实现的相关样例。可以查看Task类的API文档,来学习如何正确创建Task实现的更多样例。

打赏一下
支付宝
微信
除非注明,博客文章均为原创,转载请标明文章地址
本文地址: http://www.javafxchina.net/blog/2015/09/doc1301-concurrency/
百度已收录