如何确定取消的ScheduledFuture是否实际上没有取消?

时间:2019-04-30 14:15:09

标签: java concurrency scheduled-tasks java.util.concurrent

我正在使用ScheduledExecutorService并提交这样的任务:

future = scheduledExecutorService.schedule(myRunnableTask, delay, timeunit)

但是,一定时间后可能会发生某个事件,这表明不再需要此任务。因此,我需要取消此任务,并且正在使用

boolean cancelled = future.cancel(false)行。

取消后,我必须根据提交的可运行对象是否实际运行来采取不同的操作。这里首先让我们进入Oracle文档并阅读cancelled标志的含义:

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html#cancel(boolean)

  

返回:如果无法取消任务(通常是因为它已经正常完成),则返回false;否则,返回false。否则为真

这就是返回值的全部内容。似乎写此文本行的人不确定false在这里的返回值,但我认为我可以接受。

现在让我们关注案件,当案件返回true时。这里有两种可能性:

  1. 该任务实际上已取消,并且无法运行。
  2. 可运行对象正在运行中,因此无法取消。 (除非我执行一些线程中断逻辑,而这并不是我真正想要的)

我对这两种情况都可以,但我想知道实际发生的一种并采取相应的措施。如果可运行对象正在运行中,那么我可以完成它的工作,我想等待它完成,然后做一件事。但是,如果它被取消并且根本不会运行,我想做另一件事。

您能推荐一种解决方法吗?我想念什么吗?

2 个答案:

答案 0 :(得分:1)

您可以使用以下方法实现目标:

  • 一个Set<Runnable>,用于跟踪已由线程池开始执行的Runnable
  • Map<ScheduledFuture<?>, Runnable>映射到其各自的ScheduledFuture<?>的{​​{1}}。
    • 安排任务后,应立即将Runnable及其相应的ScheduledFuture添加到Runnable
    • 如果这种插入Map的操作是通过计划任务本身来自动完成的,那么就可以避免这样的极端情况:即使在Map被执行之后,也从未将ScheduledFuture添加到Map中。已取消。

我建议将ScheduledExecutorService更改为ScheduledThreadPoolExecutor,这将允许您覆盖其beforeExecute(Thread, Runnable)方法;在为任务分配了执行任务的线程之后,池将在任务运行之前立即调用此方法。

覆盖此方法时,可以将Runnable添加到Set<Runnable>中。

然后,当取消ScheduledFuture时,可以调用set.contains(map.get(future)),它告诉您是否执行了Runnable(映射到ScheduledFuture)。


请注意,您的Set<Runnable>Map<ScheduledFuture<?>, Runnable>实现可能必须是线程安全的,以避免可能出现的竞争情况。

答案 1 :(得分:1)

我最终为此问题写了类似的东西。源代码和一些单元测试可以在https://github.com/nuzayats/cancellabletaskexecutor

中找到
public class CancellableTaskExecutor {

    private final ScheduledExecutorService es;
    private final Logger log;

    /**
     * For a unit test to replicate a particular timing
     */
    private final Runnable hookBetweenCancels;

    public CancellableTaskExecutor(ScheduledExecutorService es, Logger log) {
        this(es, log, () -> {
            // nop
        });
    }

    // For unit tests
    CancellableTaskExecutor(ScheduledExecutorService es, Logger log, Runnable hookBetweenCancels) {
        this.es = es;
        this.log = log;
        this.hookBetweenCancels = hookBetweenCancels;
    }

    public Execution schedule(Runnable task, long delay, TimeUnit unit) {
        CancellableRunnable runnable = new CancellableRunnable(task);
        ScheduledFuture<?> future = es.schedule(runnable, delay, unit);
        return new Execution(future, runnable);
    }

    public class Execution {

        private final ScheduledFuture<?> future;
        private final CancellableRunnable runnable;

        private Execution(ScheduledFuture<?> future, CancellableRunnable runnable) {
            this.future = future;
            this.runnable = runnable;
        }

        /**
         * @return true when the task has been successfully cancelled and it's guaranteed that
         * the task won't get executed. otherwise false
         */
        public boolean cancel() {
            boolean cancelled = runnable.cancel();
            hookBetweenCancels.run();

            // the return value of this call is unreliable; see https://stackoverflow.com/q/55922874/3591946
            future.cancel(false);

            return cancelled;
        }
    }

    private class CancellableRunnable implements Runnable {

        private final AtomicBoolean cancelledOrStarted = new AtomicBoolean();
        private final Runnable task;

        private CancellableRunnable(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            if (!cancelledOrStarted.compareAndSet(false, true)) {
                return; // cancelled, forget about the task
            }
            try {
                task.run();
            } catch (Throwable e) {
                log.log(Level.WARNING, "Uncaught Exception", e);
            }
        }

        boolean cancel() {
            return cancelledOrStarted.compareAndSet(false, true);
        }
    }
}