【问题标题】:Properly cancel JavaFX Task that launches ExecutorService正确取消启动 ExecutorService 的 JavaFX 任务
【发布时间】:2016-06-28 15:25:34
【问题描述】:

我正在尝试编写一个 GUI 应用程序来执行许多计算密集型任务。因为这些任务需要一些时间,所以我想使用ExecutorService 在多个线程上运行它们。但是,等待这些任务完成会冻结 UI,因此我将其作为 Task 在其自己的线程中运行,它会根据 ExecuterService 的进度更新 UI。用户使用Start 按钮启动任务,并且应该能够使用Cancel 取消它们。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
import javafx.scene.Scene;
import javafx.stage.Stage;

class DoWorkTask extends Task<List<Double>> {

    // List of tasks to do in parallel
    private final List<Callable<Double>> tasks;

    // Initialize the tasks
    public DoWorkTask(final int numTasks) {

        this.tasks = new ArrayList<>();

        for (int i = 0; i < numTasks; ++i) {
            final int num = i;
            final Callable<Double> task = () -> {
                System.out.println("task " + num + " started");
                return longFunction();
            };

            this.tasks.add(task);
        }
    }

    @Override
    protected List<Double> call() {

        final ExecutorService executor = Executors.newFixedThreadPool(4);

        // Submit all tasks to the ExecutorService
        final List<Future<Double>> futures = new ArrayList<>();
        this.tasks.forEach(task -> futures.add(executor.submit(task)));

        final List<Double> result = new ArrayList<>();

        // Calling task.cancel() breaks out of this
        // function without completing the loop
        for (int i = 0; i < futures.size(); ++i) {
            System.out.println("Checking future " + i);

            final Future<Double> future = futures.get(i);

            if (this.isCancelled()) {
                // This code is never run
                System.out.println("Cancelling future " + i);
                future.cancel(false);
            } else {
                try {
                    final Double sum = future.get();
                    result.add(sum);
                } catch (InterruptedException | ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        executor.shutdown();
        return result;
    }

    // Some computationally intensive function
    private static Double longFunction() {

        double sum = 0;
        for (int i = 0; i < 10000000; ++i) {
            sum += Math.sqrt(i);
        }

        return sum;
    }

}

public class Example extends Application {

    final Button btnStart = new Button("Start");
    final Button btnCancel = new Button("Cancel");
    final HBox box = new HBox(10, btnStart, btnCancel);
    final Scene scene = new Scene(box);

    @Override
    public void start(final Stage stage) {

        box.setPadding(new Insets(10));

        btnStart.setOnAction(event -> {

            final DoWorkTask task = new DoWorkTask(100);

            btnCancel.setOnAction(e -> task.cancel());

            task.setOnSucceeded(e -> System.out.println("Succeeded"));

            task.setOnCancelled(e -> System.out.println("Cancelled"));

            task.setOnFailed(e -> {
                System.out.println("Failed");
                throw new RuntimeException(task.getException());
            });

            new Thread(task).start();
        });

        stage.setScene(scene);
        stage.show();
    }
}

但是,在启动任务并按下Cancel 按钮后,call() 函数似乎立即结束,而不会遍历其余的期货。一些示例输出。

task 0 started
task 1 started
task 2 started
Checking future 0
task 3 started
Checking future 1
task 4 started
Checking future 2
task 5 started
Cancelled
// Should continue to print Checking future x and
// Cancelling future x, but doesn't
task 6 started
task 7 started
task 8 started
task 9 started
...

剩余的期货都没有被取消,而且似乎它们甚至没有被迭代; call() 函数立即结束。如果可调用对象不在ExecutorService 内运行,而只是在DoWorkTask 内按顺序运行,则不会出现此问题。我比较疑惑。

【问题讨论】:

    标签: java multithreading javafx


    【解决方案1】:

    你的问题在这里:

    try {
        final Double sum = future.get();
        result.add(sum);
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }
    

    当您单击按钮取消时,它实际上取消了主任务DoWorkTask,这会中断执行主任务的线程,因此它正在等待future.get() 的结果,引发InterruptedException,但然后你当前的代码抛出一个RuntimeException,这样你的主任务立即退出,因此不能中断子任务,你应该在抛出InterruptedException时继续。

    try {
        final Double sum = future.get();
        result.add(sum);
    } catch (ExecutionException e) {
        throw new RuntimeException(e);
    } catch (InterruptedException e) {
        // log something here to indicate that the task has been interrupted.
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-24
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 2018-01-05
    • 1970-01-01
    相关资源
    最近更新 更多