正如this answer所提到的,您的要求似乎是使用Service的完美原因.服务允许您以可重用的方式在任何给定时间运行一个任务.当您通过Service.cancel()取消服务时,它会取消基础任务.该服务还会为您跟踪自己的任务,因此您无需将它们保存在某个列表中.
使用您的MVCE您想要做的是创建一个包装您的任务的服务.每次用户在ListView中选择新项目时,您都将取消服务,更新必要的状态,然后重新启动服务.然后,您将使用Service.setOnSucceeded回调将结果设置为Label.这可以保证只返回最后一次成功执行.即使先前取消的任务仍然返回结果,服务也会忽略它们.
您也不必担心外部同步(至少在您的MVCE中).处理启动,取消和观察服务的所有操作都发生在FX线程上.在FX线程上没有执行的唯一一段代码(如下所示)将在Task.call()内部(好吧,以及在实例化类时我们认为发生在JavaFX-Launcher线程上的直接分配的字段).
以下是使用服务修改的MVCE版本:
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private final ListViewFailed(wse -> {
// you could also show an Alert to the user here
service.getException().printStackTrace();
service.reset();
});
// Simple UI
VBox root = new VBox(5);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
root.getChildren().addAll(listView,"Five"
);
listView.getSelectionModel().selectedItemProperty().addListener((observableValue,newValue) -> {
if (service.isRunning()) {
service.cancel();
service.reset();
}
service.setSelected(newValue);
service.start();
});
stage.setScene(new Scene(root));
stage.show();
}
private static class QueryService extends Service
当您调用Service.start()时,它会创建一个Task并使用其executor property中包含的当前Executor执行它.如果该属性包含null,则它使用一些未指定的默认Executor(使用守护程序线程).
在上面,你看到我取消后在onSucceeded和onFailed回调中调用reset().这是因为只有在READY state中才能启动服务.如果需要,可以使用restart()而不是start().它基本上等同于调用cancel() – > reset() – > start().
1任务无法恢复.相反,服务每次启动时都会创建一个新任务.
取消服务时,取消当前正在运行的任务(如果有).即使服务,因此任务,已被取消并不意味着执行已经实际停止.在Java中,取消后台任务需要与所述任务的开发者合作.
这种合作采取定期检查执行是否应该停止的形式.如果使用普通的Runnable或Callable,则需要检查当前Thread的中断状态或使用一些boolean flag2.由于Task扩展了FutureTask,您还可以使用从Future接口继承的isCancelled()方法.如果由于某种原因你不能使用isCancelled()(称为外部代码,不使用任务等等),那么你使用以下方法检查线程中断:
>静态方法
>只能检查当前线程
>清除当前线程的中断状态
>实例方法
>可以检查您有引用的任何线程
>不清除中断状态
您可以通过Thread.currentThread()获取对当前线程的引用.
在您的后台代码中,如果当前线程已被中断,布尔标志已设置或任务已被取消,您需要在适当的位置进行检查.如果有,那么你将执行任何必要的清理并停止执行(通过返回或抛出异常).
此外,如果您的线程正在等待某些可中断操作,例如阻塞IO,那么它将在中断时抛出InterruptedException.在您的MVCE中,您使用Thread.sleep,它是可中断的;这意味着当你调用cancel时,这个方法会抛出提到的异常.
当我说上面的“清理”时,我的意思是在后台需要清理,因为你还在后台线程上.如果您需要清理FX线程上的任何内容(例如更新UI),那么您可以使用Service的onCancelled属性.
在上面的代码中,您还将看到我使用protected methods succeeded()和cancel(). Task和Service都提供了这些方法(以及各种Worker.States的其他方法),并且它们将始终在FX线程上调用.但是,请阅读文档,因为ScheduledService要求您调用其中某些方法的超级实现.
2如果使用布尔标志,请确保其他线程可以看到对它的更新.您可以通过使其变得易变,同步或使用java.util.concurrent.atomic.AtomicBoolean来实现此目的.