如何在一定数量的执行后停止计划重复执行的 Runnable

2022-08-31 14:35:00

情况

我有一个Runnable。我有一个类,它使用SchudmentEdExecutorService和schuleWithFixedDelay来调度这个Runnable执行。

目标

我想更改此类以无限期地安排Runnable进行固定延迟执行,或者直到它运行了一定次数,具体取决于传递给构造函数的某些参数。

如果可能的话,我想使用相同的Runnable,因为它在概念上是同一个应该“运行”的东西。

可能的方法

方法#1

有两个 Runnable,一个在多次执行后取消计划(它会保留计数),另一个不会:

public class MyClass{
    private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    public enum Mode{
        INDEFINITE, FIXED_NO_OF_TIMES
    }

    public MyClass(Mode mode){
        if(mode == Mode.INDEFINITE){
            scheduler.scheduleWithFixedDelay(new DoSomethingTask(), 0, 100, TimeUnit.MILLISECONDS);
        }else if(mode == Mode.FIXED_NO_OF_TIMES){
            scheduler.scheduleWithFixedDelay(new DoSomethingNTimesTask(), 0, 100, TimeUnit.MILLISECONDS);
        }
    }

    private class DoSomethingTask implements Runnable{
        @Override
        public void run(){
            doSomething();
        }
    }

    private class DoSomethingNTimesTask implements Runnable{
        private int count = 0;

        @Override
        public void run(){
            doSomething();
            count++;
            if(count > 42){
                // Cancel the scheduling.
                // Can you do this inside the run method, presumably using
                // the Future returned by the schedule method? Is it a good idea?
            }
        }
    }

    private void doSomething(){
        // do something
    }
}

我宁愿只有一个Runnable用于执行doSomething方法。将调度绑定到 Runnable 感觉不对。对此,你怎么看?

方法#2

有一个 Runnable 用于执行我们要定期运行的代码。有一个单独的计划运行,用于检查第一个Runnable的运行次数,并在达到一定数量时取消。这可能不准确,因为它是异步的。感觉有点麻烦。对此,你怎么看?

方法#3

Extend ScheduledExecutorService 并添加一个方法 “scheduleWithFixedDelayNTimes”。也许这样的类已经存在?目前,我正在使用我的 ScheduledExecutorService 实例。我大概必须实现类似的功能来实例化扩展的 ScheduledExecutorService。这可能很棘手。对此,你怎么看?Executors.newSingleThreadScheduledExecutor();

无调度程序方法 [编辑]

我无法使用调度程序。相反,我可以有这样的东西:

for(int i = 0; i < numTimesToRun; i++){
    doSomething();
    Thread.sleep(delay);
}

并在某个线程中运行它。对此,你怎么看?您可能仍然可以使用 runnable 并直接调用 run 方法。


欢迎任何建议。我正在寻找一场辩论,以找到实现目标的“最佳实践”方式。


答案 1

您可以在 Future 上使用 cancel() 方法。来自 scheduleAtFixedRate 的 javadocs

Otherwise, the task will only terminate via cancellation or termination of the executor

下面是一些示例代码,它将 Runnable 包装在另一个跟踪原始运行次数的另一个代码中,并在运行 N 次后取消。

public void runNTimes(Runnable task, int maxRunCount, long period, TimeUnit unit, ScheduledExecutorService executor) {
    new FixedExecutionRunnable(task, maxRunCount).runNTimes(executor, period, unit);
}

class FixedExecutionRunnable implements Runnable {
    private final AtomicInteger runCount = new AtomicInteger();
    private final Runnable delegate;
    private volatile ScheduledFuture<?> self;
    private final int maxRunCount;

    public FixedExecutionRunnable(Runnable delegate, int maxRunCount) {
        this.delegate = delegate;
        this.maxRunCount = maxRunCount;
    }

    @Override
    public void run() {
        delegate.run();
        if(runCount.incrementAndGet() == maxRunCount) {
            boolean interrupted = false;
            try {
                while(self == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
                self.cancel(false);
            } finally {
                if(interrupted) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    public void runNTimes(ScheduledExecutorService executor, long period, TimeUnit unit) {
        self = executor.scheduleAtFixedRate(this, 0, period, unit);
    }
}

答案 2

引自API描述(ScheduledExecutorService.scheduleWithFixedDelay):

创建并执行一个定期操作,该操作在给定的初始延迟之后首先启用,然后在一个执行终止和下一个执行开始之间的给定延迟下一个执行期间启用。如果任务的任何执行遇到异常,则禁止后续执行。否则,任务将仅通过取消或终止执行程序而终止。

因此,最简单的方法是“只是抛出一个异常”(即使这被认为是不好的做法):

static class MyTask implements Runnable {

    private int runs = 0;

    @Override
    public void run() {
        System.out.println(runs);
        if (++runs >= 20)
            throw new RuntimeException();
    }
}

public static void main(String[] args) {
    ScheduledExecutorService s = Executors.newSingleThreadScheduledExecutor();
    s.scheduleWithFixedDelay(new MyTask(), 0, 100, TimeUnit.MILLISECONDS);
}

推荐