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.