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

Spy*_*cho 57 java scheduling scheduled-tasks

情况

我有一个Runnable.我有一个类,使用ScheduledExecutorService和scheduleWithFixedDelay来调度此Runnable以执行.

目标

我想改变这个班安排了Runnable固定延迟执行要么无限期,直到它已运行一定的次数,取决于被传递给构造函数的参数一些.

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

可能的方法

方法#1

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我宁愿只有一个Runnable来执行doSomething方法.将调度绑定到Runnable感觉不对.你怎么看待这件事?

方法#2

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

方法#3

扩展ScheduledExecutorService并添加方法"scheduleWithFixedDelayNTimes".也许这样的课程已经存在?目前,我正在使用Executors.newSingleThreadScheduledExecutor();我的ScheduledExecutorService实例.我可能必须实现类似的功能来实例化扩展的ScheduledExecutorService.这可能很棘手.你怎么看待这件事?

没有调度程序方法[编辑]

我无法使用调度程序.我可以改为:

for(int i = 0; i < numTimesToRun; i++){
    doSomething();
    Thread.sleep(delay);
}
Run Code Online (Sandbox Code Playgroud)

并在某个线程中运行它.你对那个怎么想的?您可能仍然可以使用runnable并直接调用run方法.


欢迎任何建议.我正在寻找辩论,找到实现目标的"最佳实践"方式.

sbr*_*ges 62

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

Otherwise, the task will only terminate via cancellation or termination of the executor
Run Code Online (Sandbox Code Playgroud)

下面是一些示例代码,它将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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这几乎是@JB Nizet的建议.我知道如何取消预定的Runnable.我想知道的是在这种情况下取​​消它的最合适的方法.您的解决方案将调度与Runnable本身联系在一起,我不相信. (3认同)
  • 太棒了 我误解了你最初说的话.我明白你的意思.因此,您要包装要在另一个负责运行N次的Runnable中运行的Runnable.这对我来说听起来不错.它将调度与实际的Runnable分开,并允许重复使用FixedExecutionRunnable runnable类.其他人怎么看待这种方法? (3认同)

dac*_*cwe 8

引用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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这意味着在非特殊情况下抛出"例外"......我真的不喜欢那种语义...... (7认同)
  • 它类似于抛出InterruptedException,对我来说似乎是最明显和最干净的解决方案. (3认同)
  • 我认为抛出异常只是在非特殊情况下取消执行是一个可怕的想法,特别是因为有适当的方法(比如在sbridges中回答)来停止执行. (3认同)

Jan*_*net 6

到目前为止,sbridges解决方案似乎是最干净的解决方案,除了你提到的,它留下了处理执行次数的责任Runnable.它不应该与此有关,而是重复应该是处理调度的类的参数.为实现这一目标,我建议采用以下设计,为其引入一个新的执行器类Runnables.该类提供了两种用于调度任务的公共方法,这些方法是标准的Runnables,具有有限或无限重复.同样Runnable可以传递为有限和无限调度,如果需要的话(这是不可能的所有提出的解决方案,扩展Runnable类来提供有限重复).取消有限重复的处理完全封装在调度程序类中:

class MaxNScheduler
{

  public enum ScheduleType 
  {
     FixedRate, FixedDelay
  }

  private ScheduledExecutorService executorService =
     Executors.newSingleThreadScheduledExecutor();

  public ScheduledFuture<?> scheduleInfinitely(Runnable task, ScheduleType type, 
    long initialDelay, long period, TimeUnit unit)
  {
    return scheduleNTimes(task, -1, type, initialDelay, period, unit);
  }

  /** schedule with count repetitions */
  public ScheduledFuture<?> scheduleNTimes(Runnable task, int repetitions, 
    ScheduleType type, long initialDelay, long period, TimeUnit unit) 
  {
    RunnableWrapper wrapper = new RunnableWrapper(task, repetitions);
    ScheduledFuture<?> future;
    if(type == ScheduleType.FixedDelay)
      future = executorService.scheduleWithFixedDelay(wrapper, 
         initialDelay, period, TimeUnit.MILLISECONDS);
    else
      future = executorService.scheduleAtFixedRate(wrapper, 
         initialDelay, period, TimeUnit.MILLISECONDS);
    synchronized(wrapper)
    {
       wrapper.self = future;
       wrapper.notify(); // notify wrapper that it nows about it's future (pun intended)
    }
    return future;
  }

  private static class RunnableWrapper implements Runnable 
  {
    private final Runnable realRunnable;
    private int repetitions = -1;
    ScheduledFuture<?> self = null;

    RunnableWrapper(Runnable realRunnable, int repetitions) 
    {
      this.realRunnable = realRunnable;
      this.repetitions = repetitions;
    }

    private boolean isInfinite() { return repetitions < 0; }
    private boolean isFinished() { return repetitions == 0; }

    @Override
    public void run()
    {
      if(!isFinished()) // guard for calls to run when it should be cancelled already
      {
        realRunnable.run();

        if(!isInfinite())
        {
          repetitions--;
          if(isFinished())
          {
            synchronized(this) // need to wait until self is actually set
            {
              if(self == null)
              {
                 try { wait(); } catch(Exception e) { /* should not happen... */ }
              }
              self.cancel(false); // cancel gracefully (not throwing InterruptedException)
            }
          }
        }
      }
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

公平地说,管理重复的逻辑仍然是a Runnable,但它是Runnable完全内部的MaxNScheduler,而Runnable为调度传递的任务不必关心调度的本质.此外,如果需要,可以通过每次RunnableWrapper.run执行时提供一些回调,将此问题轻松移出到调度程序中.这会使代码稍微复杂化,并且需要保留一些RunnableWrappers的映射和相应的重复,这就是为什么我选择将计数器保留在RunnableWrapper类中的原因.

设置self时,我还在包装器上添加了一些同步.理论上需要这样做,当执行结束时,可能还没有分配自我(一个相当理论的场景,但只能重复一次).

取消是优雅地处理,没有抛出,InterruptedException并且在执行取消之前的情况下,安排另一轮,RunnableWrapper不会调用底层Runnable.