So stoppen Sie ein zur wiederholten Ausführung geplantes Runnable nach einer bestimmten Anzahl von Ausführungen

Lesezeit: 14 Minuten

Benutzer-Avatar
Spycho

Lage

Ich habe ein Runnable. Ich habe eine Klasse, die dieses Runnable für die Ausführung mit einem ScheduledExecutorService mit plant scheduleWithFixedDelay.

Tor

Ich möchte diese Klasse ändern, um das Runnable für die Ausführung mit fester Verzögerung zu planen entweder unbegrenzt, oder bis es eine bestimmte Anzahl von Malen ausgeführt wurde, abhängig von einigen Parametern, die an den Konstruktor übergeben werden.

Wenn möglich, möchte ich dasselbe Runnable verwenden, da es konzeptionell dasselbe ist, was “ausgeführt” werden sollte.

Mögliche Ansätze

Ansatz Nr. 1

Haben Sie zwei Runnables, eines, das den Zeitplan nach einer Reihe von Ausführungen abbricht (die gezählt werden) und eines, das dies nicht tut:

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
    }
}

Ich hätte lieber nur ein Runnable für die Ausführung der doSomething-Methode. Die Planung an das Runnable zu binden, fühlt sich falsch an. Was denkst du darüber?

Ansatz Nr. 2

Haben Sie ein einzelnes Runnable für die Ausführung des Codes, den wir regelmäßig ausführen möchten. Haben Sie ein separates geplantes Runnable, das überprüft, wie oft das erste Runnable ausgeführt wurde, und abbricht, wenn es eine bestimmte Anzahl erreicht. Dies ist möglicherweise nicht genau, da es asynchron wäre. Es fühlt sich etwas umständlich an. Was denkst du darüber?

Ansatz Nr. 3

Erweitern Sie ScheduledExecutorService und fügen Sie eine Methode “scheduleWithFixedDelayNTimes” hinzu. Vielleicht existiert eine solche Klasse bereits? Derzeit verwende ich Executors.newSingleThreadScheduledExecutor(); um meine ScheduledExecutorService-Instanz zu erhalten. Ich müsste vermutlich eine ähnliche Funktionalität implementieren, um den erweiterten ScheduledExecutorService zu instanziieren. Das könnte schwierig sein. Was denkst du darüber?

Kein Scheduler-Ansatz [Edit]

Ich konnte keinen Planer verwenden. Ich könnte stattdessen so etwas haben wie:

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

Und führen Sie das in einem Thread aus. Was halten Sie davon? Sie könnten möglicherweise immer noch das Runnable verwenden und die Methode run direkt aufrufen.


Alle Vorschläge willkommen. Ich suche eine Debatte, um den “Best Practice”-Weg zu finden, um mein Ziel zu erreichen.

Benutzer-Avatar
Brücken

Sie können die Methode cancel() für Future verwenden. Aus den Javadocs von Fahrplan zum Festpreis

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

Hier ist ein Beispielcode, der ein Runnable in ein anderes umschließt, das nachverfolgt, wie oft das Original ausgeführt wurde, und nach dem Ausführen von N-mal abbricht.

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);
    }
}

  • Das ist ziemlich genau das, was @JB Nizet vorschlägt. Ich weiß, wie man ein geplantes Runnable storniert. Was ich wissen möchte, ist der am besten geeignete Weg, es in dieser Situation zu stornieren. Ihre Lösung bindet die Planung an das Runnable selbst, von dem ich nicht so überzeugt bin.

    – Spycho

    4. September 2011 um 14:41 Uhr

  • eine praktische Methode hinzugefügt, um eine Aufgabe n-mal auszuführen, um zu zeigen, dass die Planung nicht an das Runnable gebunden ist, das die Arbeit erledigt

    – Brücken

    4. September 2011 um 14:48 Uhr

  • Oh toll. Ich habe deine Aussage anfangs falsch verstanden. Ich verstehe was du meinst. Sie packen also das Runnable, das Sie ausführen möchten, in ein anderes Runnable, das für die N-malige Ausführung verantwortlich ist. Das klingt für mich nach einem guten Design. Es trennt das Scheduling vom eigentlichen Runnable und ermöglicht die Wiederverwendung der FixedExecutionRunnable-Runnable-Klasse. Was halten andere von diesem Ansatz?

    – Spycho

    4. September 2011 um 14:54 Uhr

  • Ich habe über diese Lösung nachgedacht … Was halten Sie davon, die runNTimes Methode? Dies würde das eigentliche Scheduling außerhalb des Runnable belassen. Das würde eine Planung zu festen Raten / mit festen Verzögerungen ermöglichen, ohne dass eine Reihe von Methoden hinzugefügt werden müssten. Alles, was das Runnable benötigen würde, wäre eine maximale Anzahl und das Delegate Runnable. Was haltet ihr davon?

    – Spycho

    5. September 2011 um 9:06 Uhr

  • Ah, das geht nicht, denn dann hätte das Runnable keinen Zugriff auf die Zukunft … Und Sie könnten die Zukunft nicht an den Konstruktor übergeben, weil sie zum Zeitpunkt der Instanziierung nicht geplant gewesen wäre.

    – Spycho

    5. September 2011 um 10:06 Uhr

Benutzer-Avatar
dacwe

Zitiert aus der API-Beschreibung (ScheduledExecutorService.scheduleWithFixedDelay):

Erstellt und führt eine periodische Aktion aus, die zuerst nach der angegebenen anfänglichen Verzögerung und anschließend mit der angegebenen Verzögerung zwischen dem Ende einer Ausführung und dem Beginn der nächsten aktiviert wird. Wenn bei einer Ausführung der Aufgabe eine Ausnahme auftritt, werden nachfolgende Ausführungen unterdrückt. Andernfalls wird die Aufgabe nur durch Stornierung oder Beendigung des Ausführenden beendet.

Also am einfachsten wäre es “Einfach eine Ausnahme auslösen” (obwohl dies als schlechte Praxis angesehen wird):

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);
}

  • Das würde bedeuten, eine “Ausnahme” unter nicht außergewöhnlichen Umständen auszulösen … Ich mag die Semantik davon nicht wirklich …

    – Spycho

    1. September 2011 um 11:38 Uhr

  • Es ist ähnlich wie das Auslösen einer InterruptedException, scheint mir die offensichtlichste und sauberste Lösung zu sein.

    – Gehirn

    1. September 2011 um 13:21 Uhr

  • Ich denke, es ist eine schreckliche Idee, eine Ausnahme auszulösen, um die Ausführung unter nicht außergewöhnlichen Umständen einfach abzubrechen, zumal es geeignete Möglichkeiten gibt (wie in der Antwort von sbridges), die Ausführung zu stoppen.

    – Janick Bernet

    11. September 2011 um 2:32 Uhr

  • Zusätzlich zu den obigen Kommentaren würde ich hinzufügen ++ ist nicht atomar, sodass Sie nicht die richtige Anzahl von Iterationen sicherstellen. Verwenden AtomicInteger.getAndIncrement stattdessen.

    – Sieg

    12. Mai 2015 um 23:15 Uhr

  • @Victory Sicher, im Allgemeinen könnte dies ein Problem sein. In diesem speziellen Fall verwende ich jedoch einen Ausführungsdienst mit einem einzelnen Thread, sodass dies keine Rolle spielen sollte

    – Dawe

    13. Mai 2015 um 11:36 Uhr

Benutzer-Avatar
Janick Bernet

Bis jetzt Brücken Die Lösung scheint die sauberste zu sein, abgesehen von dem, was Sie erwähnt haben, dass sie die Verantwortung für die Handhabung der Anzahl der Ausführungen dem überlässt Runnable selbst. Es sollte sich nicht darum kümmern, stattdessen sollten die Wiederholungen ein Parameter der Klasse sein, die das Scheduling behandelt. Um dies zu erreichen, würde ich das folgende Design vorschlagen, das eine neue Executor-Klasse für einführt Runnables. Die Klasse stellt zwei öffentliche Methoden zum Planen von Aufgaben bereit, die Standard sind Runnables, mit endlicher oder unendlicher Wiederholung. Das Gleiche Runnable kann auf Wunsch für endliches und unendliches Scheduling übergeben werden (was nicht bei allen vorgeschlagenen Lösungen möglich ist, die das erweitern Runnable Klasse zur Bereitstellung endlicher Wiederholungen). Die Behandlung des Abbrechens endlicher Wiederholungen ist vollständig in der Scheduler-Klasse gekapselt:

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)
            }
          }
        }
      }
    }
  }

}

Um fair zu sein, die Logik der Verwaltung der Wiederholungen ist immer noch vorhanden a Runnableaber es ist ein Runnable völlig intern zu den MaxNSchedulerwährend die Runnable Eine zur Terminierung übergebene Aufgabe muss sich nicht um die Art der Terminierung kümmern. Auch dieses Anliegen könnte, falls gewünscht, leicht in den Planer verschoben werden, indem jedes Mal ein Rückruf bereitgestellt wird RunnableWrapper.run wurde ausgeführt. Dies würde den Code etwas verkomplizieren und die Notwendigkeit einführen, eine Karte zu führen RunnableWrappers und die entsprechenden Wiederholungen, weshalb ich mich dafür entschieden habe, die Zähler in der zu belassen RunnableWrapper Klasse.

Ich habe auch etwas Synchronisierung auf dem Wrapper hinzugefügt, als ich das Selbst gesetzt habe. Dies wird benötigt, da theoretisch, wenn die Ausführungen beendet sind, Selbst noch nicht zugewiesen wurde (ein ziemlich theoretisches Szenario, aber nur für 1 Wiederholung möglich).

Das Abbrechen wird elegant gehandhabt, ohne eine zu werfen InterruptedException und für den Fall, dass vor der Ausführung des Abbruchs eine weitere Runde angesetzt wird, die RunnableWrapper wird den Basiswert nicht aufrufen Runnable.

  • Der Wrapper ist ein netter Ansatz. Es scheint ziemlich sauber zu sein. Sie müssten zwei weitere Methoden hinzufügen, wenn Sie eine Terminierung mit festem Intervall statt mit fester Verzögerung zulassen wollten, aber das ist nicht das Ende der Welt.

    – Spycho

    11. September 2011 um 12:31 Uhr

  • @Spycho: Sicher, die Schnittstelle sollte trotzdem erweitert werden, um die Startverzögerung, das Intervall usw. angeben zu können. Ich werde das der Antwort hinzufügen.

    – Janick Bernet

    11. September 2011 um 12:39 Uhr

Benutzer-Avatar
Jérôme Verstrynge

Hier ist mein Vorschlag (ich glaube, er behandelt alle in der Frage genannten Fälle):

public class RepeatedScheduled implements Runnable {

    private int repeatCounter = -1;
    private boolean infinite;

    private ScheduledExecutorService ses;
    private long initialDelay;
    private long delay;
    private TimeUnit unit;

    private final Runnable command;
    private Future<?> control;

    public RepeatedScheduled(ScheduledExecutorService ses, Runnable command,
        long initialDelay, long delay, TimeUnit unit) {

        this.ses = ses;
        this.initialDelay = initialDelay;
        this.delay = delay;
        this.unit = unit;

        this.command = command;
        this.infinite = true;

    }

    public RepeatedScheduled(ScheduledExecutorService ses, Runnable command,
        long initialDelay, long delay, TimeUnit unit, int maxExecutions) {

        this(ses, command, initialDelay, delay, unit);
        this.repeatCounter = maxExecutions;
        this.infinite = false;

    }

    public Future<?> submit() {

        // We submit this, not the received command
        this.control = this.ses.scheduleWithFixedDelay(this,
            this.initialDelay, this.delay, this.unit);

        return this.control;

    }

    @Override
    public synchronized void run() {

        if ( !this.infinite ) {
            if ( this.repeatCounter > 0 ) {
                this.command.run();
                this.repeatCounter--;
            } else {
                this.control.cancel(false);
            }
        } else {
            this.command.run();
        }

    }

}

Darüber hinaus ermöglicht es einer externen Partei, alles aus dem Weg zu räumen Future zurückgegeben durch die submit() Methode.

Verwendungszweck:

Runnable MyRunnable = ...;
// Repeat 20 times
RepeatedScheduled rs = new RepeatedScheduled(
    MySes, MyRunnable, 33, 44, TimeUnit.SECONDS, 20);
Future<?> MyControl = rs.submit();
...

Benutzer-Avatar
MukeshD

Für Anwendungsfälle wie Abfragen bis zu einem bestimmten Timeout können wir mit einer einfacheren Lösung vorgehen Future.get().

/* Define task */
public class Poll implements Runnable {
    @Override
    public void run() {
        // Polling logic
    }
}

/* Create executor service */
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);

/* Schedule task - poll every 500ms */
ScheduledFuture<?> future = executorService.scheduleAtFixedRate(new Poll(), 0, 500, TimeUnit.MILLISECONDS);

/* Wait till 60 sec timeout */
try {
    future.get(60, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    scheduledFuture.cancel(false);
    // Take action on timeout
}

Benutzer-Avatar
JB Niet

Ihr erster Ansatz scheint in Ordnung zu sein. Sie können beide Arten von Runnables kombinieren, indem Sie die übergeben mode -Objekt an seinen Konstruktor (oder übergeben Sie -1 als maximale Anzahl von Malen, die es ausführen muss), und verwenden Sie diesen Modus, um zu bestimmen, ob das Runnable abgebrochen werden muss oder nicht:

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

    /**
     * Constructor for no limit
     */
    private DoSomethingNTimesTask() {
        this(-1);
    }

    /**
     * Constructor allowing to set a limit
     * @param limit the limit (negative number for no limit)
     */
    private DoSomethingNTimesTask(int limit) {
        this.limit = limit;
    }

    @Override
    public void run(){
        doSomething();
        count++;
        if(limit >= 0 && count > limit){
            // Cancel the scheduling
        }
    }
}

Sie müssen die geplante Zukunft an Ihre Aufgabe übergeben, damit sie sich selbst abbricht, oder Sie lösen möglicherweise eine Ausnahme aus.

Benutzer-Avatar
ALZ

Ich habe genau die gleiche Funktionalität gesucht und gewählt org.springframework.scheduling.Trigger.

Unten ist ein vollständiges Test-Arbeitsbeispiel (sorry, wenn zu viel Code überschwemmt wird) applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:task="http://www.springframework.org/schema/task"
 xmlns:util="http://www.springframework.org/schema/util"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util/ http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

    <bean id="blockingTasksScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
        <property name="poolSize" value="10" />
    </bean>

    <task:scheduler id="deftaskScheduler" pool-size="10" />

</beans>

JAVA

package com.alz.springTests.schedulerTest;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

public class ScheduledTest {

    private static ApplicationContext applicationContext;
    private static TaskScheduler taskScheduler;

    private static final class SelfCancelableTask implements Runnable, Trigger {
        Date creationTime = new Date();
        AtomicInteger counter = new AtomicInteger(0);
        private volatile boolean shouldStop = false;
        private int repeatInterval = 3; //seconds

        @Override
        public void run() {
            log("task: run started");

            // simulate "doing job" started
            int sleepTimeMs = ThreadLocalRandom.current().nextInt(500, 2000+1);
            log("will sleep " + sleepTimeMs + " ms");
            try {
                Thread.sleep(sleepTimeMs);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // "doing job" finished

            int i = counter.incrementAndGet();
            if (i > 5) { //cancel myself
                logErr("Attempts exceeded, will mark as shouldStop");
                shouldStop = true;

            } else {
                log("task: executing cycle #"+i);
            }
        }

        @Override
        public Date nextExecutionTime(TriggerContext triggerContext) {
            log("nextExecutionTime: triggerContext.lastActualExecutionTime() " + triggerContext.lastActualExecutionTime());
            log("nextExecutionTime: triggerContext.lastCompletionTime() " + triggerContext.lastCompletionTime());
            log("nextExecutionTime: triggerContext.lastScheduledExecutionTime() " + triggerContext.lastScheduledExecutionTime());

            if (shouldStop) 
                return null;

            if (triggerContext.lastCompletionTime() == null) {
                LocalDateTime ldt = creationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS);
                return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
            } else {
                LocalDateTime ldt = triggerContext.lastCompletionTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS);
                return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());               
            }

        }

    }

    private static void log(String log) {
        System.out.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log);
    }

    private static void logErr(String log) {
        System.err.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log);
    }

    public static void main(String[] args) {

        log("main: Stated...");

        applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        taskScheduler = (TaskScheduler) applicationContext.getBean("blockingTasksScheduler");

        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = ((ThreadPoolTaskScheduler)taskScheduler).getScheduledThreadPoolExecutor();

        SelfCancelableTask selfCancelableTask = new SelfCancelableTask();
        taskScheduler.schedule(selfCancelableTask, selfCancelableTask);


        int waitAttempts = 0;
        while (waitAttempts < 30) {
            log("scheduledPool pending tasks: " + scheduledThreadPoolExecutor.getQueue().size());

            try {
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            waitAttempts++;

        }

        log("main: Done!");


    }

}

1142490cookie-checkSo stoppen Sie ein zur wiederholten Ausführung geplantes Runnable nach einer bestimmten Anzahl von Ausführungen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy