代码之家  ›  专栏  ›  技术社区  ›  Zephyr

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

  •  5
  • Zephyr  · 技术社区  · 6 年前

    我的应用程序包含 ListView 每次选择一个项目时都会启动一个后台任务。后台任务随后会在成功完成时更新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 那是被处决的。

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

    编辑:

    在我的实际应用程序中,用户从 列表视图 后台任务读取数据库(复杂的 SELECT 语句)获取与该项关联的所有信息。然后这些详细信息将显示在应用程序中。

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

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

    5 回复  |  直到 6 年前
        1
  •  3
  •   Slaw    6 年前

    this answer Service Task Service.cancel()

    ListView Service.setOnSucceeded Label

    Task.call()

    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() Executor executor property null

    reset() onSucceeded onFailed READY state restart() start() cancel()


    Runnable Callable Thread boolean FutureTask isCancelled() Future 出于某种原因(调用外部代码,而不是使用 任务 等)然后使用以下方法检查线程中断:

    您可以通过以下方式获取对当前线程的引用: Thread.currentThread() .

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

    此外,如果您的 螺纹 正在等待一些可中断的操作,例如阻塞IO,然后它将 InterruptedException 中断时。在你的MVCE中你使用 Thread.sleep 这是可中断的;这意味着当调用Cancel时,此方法将引发上述异常。

    当我在上面说“清理”时,我指的是任何必要的清理。 在背景中 因为你还在后台。如果需要清除fx线程上的任何内容(例如更新UI),则可以使用 onCancelled 的属性 服务 .

    在上面的代码中,您还会看到我使用了受保护的方法 succeeded() cancelled() . 两个 任务 服务 提供这些方法(以及 Worker.State s)并且始终在fx线程上调用它们。但是,请阅读文档 ScheduledService 要求您调用其中一些方法的超级实现。

    如果使用 布尔值 标志确保对它的更新对其他线程可见。你可以这样做 volatile ,同步它,或使用 java.util.concurrent.atomic.AtomicBoolean .

        2
  •  0
  •   Nghia Do    6 年前

    演示类

    package com.changehealthcare.pid;
    
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ThreadFun {
        public static ExecutorService threadExecutor = Executors.newFixedThreadPool(1);
        public static ServiceThread serviceThreads = new ServiceThread();
        public static Future futureService;
    
        public static void main(String[] args) throws Exception {
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            String line = "";
    
               while (line.equalsIgnoreCase("quit") == false) {
                   line = in.readLine();
                   //do something
                   if (futureService != null) {
                       System.out.println("try to cancel");
                       futureService.cancel(true);
                   }
                   futureService = threadExecutor.submit(serviceThreads);
               }
    
               in.close();
    
        }
    }
    

    ServiceThread类

    package com.changehealthcare.pid;
    
    public class ServiceThread implements Runnable {
    
        @Override
        public void run() {
            System.out.println("starting the service");
    
            boolean isResult = this.doStuff();
    
            if (isResult) {
                this.displayStuff();
            }
            System.out.println("done");
        }
    
        private boolean doStuff() {
            try {
                for (int i=0; i<=10; i++) {
                    System.out.println(i);
                    Thread.sleep(1000);
                }
    
                return true;
            }
            catch (InterruptedException e) {
                System.out.println("cancelled");
                return false;
            }
        }
    
        private void displayStuff() {
            System.out.println("display done");
        }
    }
    

    解释 演示应用程序充当system.in(键盘)的侦听器。如果按了任何字符(按Enter键),它将取消任何当前线程(如果存在),并提交一个新线程。

    服务线程演示有一个run()方法,它调用dostuf()和displaystuff()。您可以为DisplayStuff注入UI部件。在dostuff()中,它捕获interupptedException,如果发生该异常,则不执行displayStuff()。

    请检查一下,看它是否适合你的箱子。我们可以做更多的运动。

        3
  •  0
  •   Roshana Pitigala Laxmansinghsodhanohdiyala    6 年前

    打电话 javafx.concurrent.Service.cancel() 将取消当前正在运行的任何 Task ,使其抛出 InterruptedException .

    取消当前正在运行的任何任务(如果有)。状态将设置为 CANCELLED .
    ~ Service (JavaFX 8) - cancel ~

    如果需要额外的清理,可以提供 Task.onCancelled()

        4
  •  0
  •   Tomasz Linkowski    6 年前

    class SingleTaskRunner {
    
        private Task<?> lastTask = null; 
    
        void runTask(Task<?> task) {
            registerTask(task);
            new Thread(task).start();
        }
    
        private synchronized void registerTask(Task<?> task) {
            if (lastTask != null) {
                lastTask.cancel(true);
            }
            lastTask = task;
        }
    }
    

    Nghia Do's answer

    registerTask cancel Task

    ExecutorService new Thread(task).start() Thread creation is expensive


    1. Thread.sleep() Future.cancel() Future.cancel() calls Thread.interrupt()

    2. onSucceeded Task.call()

    public class TaskRace extends Application {
    
        private final ListView<String> listView = new ListView<>();
        private final Label label = new Label("Nothing selected");
        private final SingleTaskRunner runner = new SingleTaskRunner();
    
        private final long startMillis = System.currentTimeMillis();
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
    
            // 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
                    MyTask task = new MyTask();
    
                    // Update the label when the task is completed
                    task.setOnSucceeded(event -> {
                        label.setText(task.getValue());
                        println("Assigned " + task.selectedItem);
                    });
                    task.setOnCancelled(event -> println("Cancelled " + task.selectedItem));
    
                    runner.runTask(task);
                }
    
            });
    
            stage.setScene(new Scene(root));
            stage.show();
    
        }
    
        private void println(String string) {
            System.out.format("%5.2fs: %s%n", 0.001 * (System.currentTimeMillis() - startMillis), string);
        }
    
        private class MyTask extends Task<String> {
    
            final String selectedItem = listView.getSelectionModel().getSelectedItem();
    
            @Override
            protected String call() {
                int ms = new Random().nextInt(10000);
                println(String.format("Will return %s in %.2fs", selectedItem, 0.001 * ms));
    
                // Do long-running task (takes random time)
                long limitMillis = System.currentTimeMillis() + ms;
                while (System.currentTimeMillis() < limitMillis) {
                }
    
                println("Returned " + selectedItem);
                return "You have selected item: " + selectedItem;
            }
        }
    }
    
        5
  •  0
  •   Tomasz Linkowski    6 年前

    my answer Slaw's answer

    Slaw's solution


    volatile

    private volatile String lastSelectedItem = null;
    

    lastSelectedItem = task.selectedItem;
    

    if (task.selectedItem.equals(lastSelectedItem))
    

    public class TaskRaceWithoutCancelation extends Application {
    
        private final ListView<String> listView = new ListView<>();
        private final Label label = new Label("Nothing selected");
    
        private final long startMillis = System.currentTimeMillis();
    
        private volatile String lastSelectedItem = null;
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
    
            // 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
                    MyTask task = new MyTask();
                    lastSelectedItem = task.selectedItem;
    
                    // Update the label when the task is completed
                    task.setOnSucceeded(event -> {
                        if (task.selectedItem.equals(lastSelectedItem)) {
                            label.setText(task.getValue());
                            println("Assigned " + task.selectedItem);
                        }
                    });
    
                    new Thread(task).start();
                }
    
            });
    
            stage.setScene(new Scene(root));
            stage.show();
    
        }
    
        private void println(String string) {
            System.out.format("%5.2fs: %s%n", 0.001 * (System.currentTimeMillis() - startMillis), string);
        }
    
        private class MyTask extends Task<String> {
    
            final String selectedItem = listView.getSelectionModel().getSelectedItem();
    
            @Override
            protected String call() {
                int ms = new Random().nextInt(10000);
                println(String.format("Will return %s in %.2fs", selectedItem, 0.001 * ms));
    
                // Do long-running task (takes random time)
                long limitMillis = System.currentTimeMillis() + ms;
                while (System.currentTimeMillis() < limitMillis) {
                }
    
                println("Returned " + selectedItem);
                return "You have selected item: " + selectedItem;
            }
        }
    }