简体   繁体   English

如何识别被取消的 ScheduledFuture 是否真的没有被取消?

[英]How to identify if cancelled ScheduledFuture is actually not cancelled?

I'm using a ScheduledExecutorService and submitting a task like that:我正在使用 ScheduledExecutorService 并提交这样的任务:

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

However a certain event might occur after indefinite amount of time, which signals that this task is no longer needed.然而,某个事件可能会在不确定的时间后发生,这表明不再需要该任务。 And so I need to cancel this task, and I am using所以我需要取消这个任务,我正在使用

boolean cancelled = future.cancel(false) line. boolean cancelled = future.cancel(false)行。

After cancelling, I have to take different actions depending on whether the submitted runnable actually ran or not.取消后,我必须根据提交的 runnable 是否实际运行来采取不同的操作。 And here lets first jump into Oracle Documentation and read what cancelled flag means:在这里让我们首先跳转到 Oracle 文档并阅读cancelled标志的含义:

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

Returns: false if the task could not be cancelled, typically because it has already completed normally;返回:如果任务无法取消,则返回 false,通常是因为它已经正常完成; true otherwise否则为真

That's all it says about the return value.这就是关于返回值的全部内容。 Seems like the person who wrote this text line was uncertain about false return value here, but I think I can take it.似乎写这行文本的人不确定这里的false返回值,但我想我可以接受。

Lets now focus on the case, when it returns true .现在让我们关注案例,当它返回true时。 There are two possibilities here:这里有两种可能:

  1. The task was actually cancelled and runnable never ran.该任务实际上已被取消,并且 runnable 从未运行过。
  2. The runnable is in the process of running and thus cannot be cancelled. runnable 正在运行,因此无法取消。 (unless I do some thread interrupting logic, which I don't really want to do) (除非我做了一些我不想做的线程中断逻辑)

I am okay with both cases occurring, but I want to KNOW which one actually occurred and take actions accordingly.我对这两种情况的发生都没有意见,但我想知道实际发生了哪一种并采取相应的行动。 If the runnable is in the process, then I am okay with it finishing it's job, I want to wait for it's completion and then do one thing.如果 runnable 正在处理中,那么我可以接受它完成它的工作,我想等待它完成然后做一件事。 But if it was cancelled and never going to run at all, I want to do another thing.但如果它被取消并且根本不会运行,我想做另一件事。

Can you please recommend an approach to this?你能推荐一种方法吗? Am I missing something?我错过了什么吗?

You can accomplish your goal with the following:您可以通过以下方式实现您的目标:

  • A Set<Runnable> that keeps track of Runnable s that have begun execution by the thread pool.一个Set<Runnable> ,用于跟踪已开始由线程池执行的Runnable
  • A Map<ScheduledFuture<?>, Runnable> that maps a ScheduledFuture<?> to its respective Runnable .将 ScheduledFuture< ScheduledFuture<?> > 映射到其各自的RunnableMap<ScheduledFuture<?>, Runnable>
    • After scheduling the task, you should immediately add the ScheduledFuture and its respective Runnable to the Map .调度任务后,您应该立即将ScheduledFuture及其各自的Runnable添加到Map中。
    • If this insertion into the Map is performed atomically with scheduling the task itself, then you can avoid the edge case that the ScheduledFuture was never added to the Map even after it was cancelled.如果这种插入Map的操作是通过调度任务本身以原子方式执行的,那么您可以避免ScheduledFuture即使在被取消后也从未添加到Map的边缘情况。

I recommend changing your ScheduledExecutorService to a ScheduledThreadPoolExecutor , which will allow you to override its beforeExecute(Thread, Runnable) method;我建议将您的ScheduledExecutorService更改为ScheduledThreadPoolExecutor ,这将允许您覆盖其beforeExecute(Thread, Runnable)方法; this method is invoked immediately before the task is run by the pool after it has already been assigned a thread that will execute the task.在已经为池分配了将执行任务的线程之后,池运行任务之前立即调用此方法。

When overriding this method, you can add the Runnable to your Set<Runnable> .覆盖此方法时,您可以将Runnable添加到您的Set<Runnable>中。

Then, when a ScheduledFuture is cancelled, you can call set.contains(map.get(future)) , which tells you if the Runnable (that the ScheduledFuture maps to) was executed.然后,当ScheduledFuture被取消时,您可以调用set.contains(map.get(future)) ,它会告诉您RunnableScheduledFuture映射到的)是否已执行。


Note that your Set<Runnable> and Map<ScheduledFuture<?>, Runnable> implementations may have to be made thread-safe to avoid possible race conditions.请注意,您的Set<Runnable>Map<ScheduledFuture<?>, Runnable>实现可能必须是线程安全的,以避免可能的竞争条件。

I ended up writing something like this for this issue.我最终为这个问题写了这样的东西。 Source code and some unit tests can be found at https://github.com/nuzayats/cancellabletaskexecutor源代码和一些单元测试可以在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);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM