java - 如果启动了新的任务实例,如何取消任务?

标签 java java.util.concurrent

我的应用程序包含一个 ListView每次选择项目时都会启动后台任务。后台任务成功完成后会更新 UI 上的信息。

然而,当用户快速点击一个又一个项目时,所有这些任务都会继续,最后一个完成的任务“获胜”并更新 UI,无论最后选择哪个项目。

我需要以某种方式确保此任务在任何给定时间只运行一个实例,因此在开始新任务之前取消所有先前的任务。

这是演示该问题的 MCVE:

import javafx.application.Application;
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 taskRace  extends Application {

    private final ListView<String> listView = new ListView<>();
    private final Label label = new Label("Nothing selected");
    private String labelValue;

    public static void main(String[] args) {

        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        // Simple UI
        VBox root = new VBox(5);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));
        root.getChildren().addAll(listView, label);

        // Populate the ListView
        listView.getItems().addAll(
                "One", "Two", "Three", "Four", "Five"
        );

        // Add listener to the ListView to start the task whenever an item is selected
        listView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {

            if (newValue != null) {

                // Create the background task
                Task task = new Task() {
                    @Override
                    protected Object call() throws Exception {

                        String selectedItem = listView.getSelectionModel().getSelectedItem();

                        // Do long-running task (takes random time)
                        long waitTime = (long)(Math.random() * 15000);
                        System.out.println("Waiting " + waitTime);
                        Thread.sleep(waitTime);
                        labelValue = "You have selected item: " + selectedItem ;
                        return null;
                    }
                };

                // Update the label when the task is completed
                task.setOnSucceeded(event ->{
                    label.setText(labelValue);
                });

                new Thread(task).start();
            }

        });

        stage.setScene(new Scene(root));
        stage.show();

    }
}

当以随机顺序单击多个项目时,结果是不可预测的。我需要的是更新标签以显示最后一次 Task 的结果那被执行了。

我是否需要以某种方式安排任务或将它们添加到服务中以取消所有先前的任务?

编辑:

在我的实际应用程序中,用户从 ListView 中选择一个项目。后台任务读取数据库(一个复杂的 SELECT 语句)以获取与该项目关联的所有信息。然后这些详细信息会显示在应用程序中。

发生的问题是,当用户选择一个项目但更改了他们的选择时,应用程序中显示的返回数据可能是所选的第一个项目,即使现在选择了一个完全不同的项目。

从第一个(即:不需要的)选择返回的任何数据都可以完全丢弃。

最佳答案

您的要求,为this answer提到,似乎是使用 Service 的完美理由.一个 Service允许您运行一个 Task在任何给定时间以可重复使用的方式1。当您取消 Service , 通过 Service.cancel() ,它取消了基础Task . Service还跟踪自己的Task为您服务,因此您无需将它们保存在某个列表中。

使用您的 MVCE 您想要做的是创建一个 Service包裹您的 Task .每次用户在 ListView 中选择一个新项目时你会取消 Service ,更新必要的状态,然后重新启动 Service .然后你会使用 Service.setOnSucceeded回调将结果设置为 Label .这保证只有最后一次成功的执行才会返回给您。即使之前取消 Task s 仍然返回结果 Service会忽略它们。

您也无需担心外部同步(至少在您的 MVCE 中)。所有处理启动、取消和观察 Service 的操作发生在 FX 线程上。唯一没有在 FX 线程上执行的代码(如下所示)将在 Task.call() 中。 (好吧,以及当类被实例化时立即分配的字段,我相信这发生在 JavaFX-Launcher 线程上)。

这是使用 Service 的 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 ListView<String> listView = new ListView<>();
    private final Label label = new Label("Nothing selected");

    private final QueryService service = new QueryService();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        service.setOnSucceeded(wse -> {
            label.setText(service.getValue());
            service.reset();
        });
        service.setOnFailed(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, label);

        // Populate the ListView
        listView.getItems().addAll(
                "One", "Two", "Three", "Four", "Five"
        );

        listView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, 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<String> {

        // Field representing a JavaFX property
        private String selected;

        private void setSelected(String selected) {
            this.selected = selected;
        }

        @Override
        protected Task<String> createTask() {
            return new Task<>() {

                // Task state should be immutable/encapsulated
                private final String selectedCopy = selected;

                @Override
                protected String call() throws Exception {
                    try {
                        long waitTime = (long) (Math.random() * 15_000);
                        System.out.println("Waiting " + waitTime);
                        Thread.sleep(waitTime);
                        return "You have selected item: " + selectedCopy;
                    } catch (InterruptedException ex) {
                        System.out.println("Task interrupted!");
                        throw ex;
                    }
                }

            };
        }

        @Override
        protected void succeeded() {
            System.out.println("Service succeeded.");
        }

        @Override
        protected void cancelled() {
            System.out.println("Service cancelled.");
        }

    }
}

当您拨打 Service.start()它创建了一个 Task并使用当前 Executor 执行它包含在其 executor property 中.如果该属性包含 null然后它使用一些未指定的默认值 Executor (使用守护线程)。

上面,你看我打电话 reset() 取消后在onSucceededonFailed回调。这是因为 Service只能在 READY 中启动state .您可以使用 restart() 而不是 start()如果需要的话。基本上相当于调用cancel() -> reset() -> start() .

1Task不会变成可恢复的。相反,Service创建一个新的 Task每次启动。

当您取消 Service它取消当前正在运行的 Task ,如果有的话。即使 Service ,因此 Task , 已取消 并不意味着执行实际上已经停止 .在 Java 中,取消后台任务需要与该任务的开发者合作。

这种合作采取定期检查执行是否应该停止的形式。如果使用普通 RunnableCallable这将需要检查当前 Thread 的中断状态或使用一些 boolean标志2。自 Task扩展 FutureTask 您也可以使用 isCancelled()方法继承自 Future 界面。如果您不能使用 isCancelled()出于某种原因(称为外部代码,不使用 Task 等...)然后您使用以下方法检查线程中断:
  • Thread.interrupted()
  • 静态方法
  • 只能查看当前Thread
  • 清除当前线程的中断状态
  • Thread.isInterrupted()
  • 实例方法
  • 可以查任何Thread您可以引用
  • 不清除中断状态

  • 您可以通过 Thread.currentThread() 获得对当前线程的引用。 .

    在您的后台代码中,如果当前线程已被中断,您需要在适当的点进行检查,boolean标志已设置,或 Task已经取消了。如果有,那么您将执行任何必要的清理并停止执行(通过返回或抛出异常)。

    另外,如果您的 Thread正在等待一些可中断的操作,比如阻塞IO,那么它会抛出一个InterruptedException中断时。在您的 MVCE 中,您使用 Thread.sleep可中断;意思是当你调用取消这个方法时会抛出提到的异常。

    当我在上面说“清理”时,我的意思是在后台进行任何必要的清理,因为您仍在后台线程上。如果您需要清理 FX 线程上的任何内容(例如更新 UI),那么您可以使用 onCancelled Service 的属性(property).

    在上面的代码中,您还会看到我使用了 protected 方法 succeeded()cancelled() .两者 TaskService提供这些方法(以及用于各种 Worker.State 的其他方法),它们将始终在 FX 线程上调用。但是,请阅读文档,如 ScheduledService要求您为其中一些方法调用 super 实现。

    2如果使用 boolean标志确保其他线程可以看到它的更新。你可以通过制作它来做到这一点 volatile ,同步它,或使用 java.util.concurrent.atomic.AtomicBoolean .

    关于java - 如果启动了新的任务实例,如何取消任务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51312677/

    相关文章:

    java - Apache James Spring 发行版未启动

    java - 同步代码块是只阻塞赋值还是阻塞整个 block 体?

    Java ExecutorService 和同步

    java - 需要一个 Java 方法的解决方案,该方法返回一个字符串值,以便在 JVM 中仅执行 n 个线程

    java - AtomicInteger incrementAndGet原子性

    java - 上传 CSV 文件并在数据网格中显示预览

    java - 重定向和 POST 方法

    java - 如何提高 Flink 中数据流实现的不同计数?

    java - 调用 fragment Activity 错误无法实例化 Activity 。无法转换为 android.app.Activity

    java - ConcurrentHashMap 中的活锁