thread termino saber reiniciar pausar hilos fuente entre ejemplos diferencia detener como codigo java scheduling scheduled-tasks

termino - reiniciar un hilo en java



Cómo detener un Runnable programado para ejecución repetida después de un cierto número de ejecuciones (5)

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


Hasta ahora, la solución de puentes parece ser la más limpia, excepto por lo que usted mencionó, que deja la responsabilidad de manejar el número de ejecuciones en Runnable . No debería preocuparse por esto, en cambio, las repeticiones deberían ser un parámetro de la clase que maneja la programación. Para lograr esto, sugeriría el siguiente diseño, que presenta una nueva clase ejecutor para Runnables . La clase proporciona dos métodos públicos para la programación de tareas, que son Runnables estándar, con repetición finita o infinita. El mismo Runnable se puede pasar para programación finita e infinita, si se desea (lo cual no es posible con todas las soluciones propuestas que extienden la clase Runnable para proporcionar repeticiones finitas). El manejo de la cancelación de repeticiones finitas está completamente encapsulado en la clase del planificador:

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

Para ser justos, la lógica de administrar las repeticiones sigue siendo Runnable , pero es Runnable completamente interno al MaxNScheduler , mientras que la tarea Runnable aprobada para la programación no debe preocuparse por la naturaleza de la programación. Además, esta inquietud se podría trasladar fácilmente al planificador si se desea, proporcionando alguna devolución de llamada cada vez que RunnableWrapper.run se ejecutara. Esto complicaría un poco el código e introduciría la necesidad de mantener algún mapa de RunnableWrapper y las repeticiones correspondientes, por lo que opté por mantener los contadores en la clase RunnableWrapper .

También agregué algo de sincronización en el contenedor cuando configuré el auto. Esto es necesario ya que, en teoría, cuando finalicen las ejecuciones, es posible que aún no se haya asignado el yo (un escenario bastante teórico, pero solo con 1 repetición posible).

La cancelación se maneja correctamente, sin lanzar una InterruptedException y en caso de que antes de que se ejecute la cancelación, se programa otra ronda, el RunnableWrapper no llamará al Runnable subyacente.


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.