java - thread - Cómo detener un Runnable programado para ejecución repetida después de un cierto número de ejecuciones




runnable java (4)

Situación

Tengo un Runnable. Tengo una clase que programa este Runnable para su ejecución utilizando un ScheduledExecutorService con scheduleWithFixedDelay .

Gol

Quiero modificar esta clase para programar Runnable para la ejecución de retardo fijo ya sea indefinidamente, o hasta que se haya ejecutado una cierta cantidad de veces, dependiendo de algún parámetro que se pase al constructor.

Si es posible, me gustaría usar el mismo Runnable, ya que conceptualmente es lo mismo que debería "ejecutarse".

Posibles enfoques

Enfoque # 1

Tener dos Runnables, uno que cancela el cronograma después de varias ejecuciones (que cuenta) y otro que no:

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

Preferiría tener solo un Runnable para la ejecución del método doSomething. Atar la programación al Runnable se siente mal. ¿Qué piensas sobre esto?

Enfoque # 2

Tener un Runnable único para la ejecución del código que queremos ejecutar periódicamente. Tener un ejecutable programable por separado que verifica cuántas veces ejecutó el primer Runnable y cancela cuando llega a una cierta cantidad. Esto puede no ser preciso, ya que sería asincrónico. Se siente un poco engorroso. ¿Qué piensas sobre esto?

Enfoque n. ° 3

Extienda ScheduledExecutorService y agregue un método "scheduleWithFixedDelayNTimes". Quizás tal clase ya existe? Actualmente, estoy usando Executors.newSingleThreadScheduledExecutor(); para obtener mi instancia de ScheduledExecutorService. Presumiblemente tendría que implementar una funcionalidad similar para instanciar el ScheduledExecutorService extendido. Esto podría ser complicado. ¿Qué piensas sobre esto?

Sin enfoque del programador [Editar]

No pude usar un programador. En cambio, podría tener algo como:

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

Y ejecuta eso en algún hilo. ¿Qué piensa usted de eso? Aún podría utilizar el ejecutable y llamar al método de ejecución directamente.

Cualquier sugerencia bienvenida Estoy buscando un debate para encontrar la forma de "mejores prácticas" para lograr mi objetivo.


Aquí está mi sugerencia (creo que maneja todos los casos mencionados en la pregunta):

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

    }

}

Además, permite que una parte externa pueda detener todo el Future devuelto por el método submit() .

Uso:

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

Citado de la descripción de API ( scheduleWithFixedDelay ):

Crea y ejecuta una acción periódica que se habilita primero después de la demora inicial dada, y posteriormente con la demora dada entre la finalización de una ejecución y el comienzo de la siguiente. Si cualquier ejecución de la tarea encuentra una excepción, las ejecuciones posteriores se suprimen. De lo contrario, la tarea solo terminará a través de la cancelación o la terminación del ejecutor.

Entonces, lo más fácil sería "lanzar una excepción" (aunque esto se considera una mala práctica):

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

Puede usar el método cancel () en Future. De los javadocs de scheduleAtFixedRate

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

Aquí hay un código de ejemplo que envuelve Runnable en otro que rastrea el número de veces que se ejecutó el original, y cancela después de ejecutar N veces.

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

Tu primer acercamiento parece estar bien. Puede combinar ambos tipos de ejecutables pasando el objeto de mode a su constructor (o pase -1 como el número máximo de veces que debe ejecutarse), y use este modo para determinar si el ejecutable debe ser cancelado o no:

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

Tendrá que pasar el futuro programado a su tarea para que se cancele a sí mismo, o puede lanzar una excepción.





scheduled-tasks