16

¿Existe alguna manera de detener la repetición de tareas desde la propia tarea cuando se ejecuta en un ScheduledExecutorService?Detener una tarea periódica desde dentro de la tarea que se ejecuta en un ScheduledExecutorService

permite decir, he la siguiente tarea:

Future<?> f = scheduledExecutor.scheduleAtFixedRate(new Runnable() { 
    int count = 0; 
    public void run() { 
     System.out.println(count++); 
     if (count == 10) { 
      // ??? cancel self 
     } 
    } 
}, 1, 1, TimeUnit.SECONDS); 

Desde fuera, es fácil de cancelar a través de f.cancel(), pero ¿cómo puedo evitar la repetición en el lugar especificado? (Pasar el futuro a través de una AtomicReference no es seguro, porque hay una ventana potencial cuando scheduleAtFixedRate devuelve f tarde y la variable también se establece tarde, y la tarea en sí misma podría haberse ejecutado, viendo un nulo en la referencia).

+0

¿Por qué no lanzar una excepción StopIteration que puede atrapar para eliminar la llamada del ejecutor desde el exterior? Por lo que sé, Callable's no tiene conocimiento del ejecutor al que están agregados. – Tim

+0

la manera más simple y bastante fea es lanzar una excepción :) – bestsss

Respuesta

11

Cuando una tarea repetitiva arroja una excepción o error, se coloca en el futuro y la tarea no se repite nuevamente. Puede lanzar una RuntimeException o Error de su elección.

+1

Sí, esa es una opción. Espero una solución más elegante. – akarnokd

+3

Esta es la mejor manera de hacerlo.Si desea que sea "bonita", declare una clase que amplíe RuntimeException que describa correctamente el propósito, como TaskComplete. –

+0

@ kd304, si piensas: esa es la razón por la que se crean las excepciones, para señalar un estado "excepcional", no vi la respuesta de Peter cuando dejé caer el comentario. Esta es una respuesta de comida (edit: bueno, maldito fuera de lugar) :) – bestsss

2

Parece que el Runnable tiene un mal diseño para saber algo sobre el ejecutor en el que se está ejecutando, o lanzar un error si llegar a 10 no es un estado de error es un hack.

¿Se puede hacer el ciclo a 10 fuera de la programación y la ejecución? Esto puede requerir el uso de un ejecutor no programado ya que usted mismo los programaría manualmente.

+0

La programación es imprescindible. – akarnokd

4

En lugar de utilizar una clase interna anónima, puede utilizar una clase con nombre que puede tener una propiedad para el objeto Future que obtiene del Executor al programar una tarea.

abstract class FutureRunnable implements Runnable { 

    private Future<?> future; 

    /* Getter and Setter for future */ 

} 

Al programar una tarea a continuación, puede pasar el Future a la Runnable.

FutureRunnable runnable = new FutureRunnable() { 

    public void run() { 
     if (/* abort condition */) 
      getFuture().cancel(false); 
    } 

}; 
Future<?> future = executor.scheduleAtFixedRate(runnable, ...); 
runnable.setFuture(future); 

Tal vez usted tendrá que asegurarse de que la tarea no se ejecuta antes de la Future se ha establecido, porque de lo contrario obtendrá un NullPointerException.

+2

un NPE cancelará efectivamente la tarea igual de bien :) – bestsss

1

Aquí hay otra manera, eso es incluso Thread safe;

final Future<?>[] f = {null}; 
    f[0]= scheduledExecutor.scheduleAtFixedRate(new Runnable() { 
     int count = 0; 
     public void run() { 

      System.out.println(count++); 
      if (count == 10) { 
       Future<?> future; 
       while(null==(future = f[0])) Thread.yield();//prevent exceptionally bad thread scheduling 
       future.cancel(false); 
       return; 
       //cancel self 
      } 
     } 
    }, 1, 1, TimeUnit.SECONDS); 
1

Acabo de ver esto ahora ... porque quería hacer lo mismo ... esta es mi solución, sospecho que esto es inseguro.

En primer lugar crear un contenedor para el futuro:

public static class Cancel { 
    private ScheduledFuture<?> future; 

    public synchronized void setFuture(ScheduledFuture<?> future) { 
     this.future = future; 
    } 

    public synchronized void stop() { 
     LOG.debug("cancelling {}", future); 
     future.cancel(false); 
    } 
} 

Y entonces el futuro código:

final Cancel controller = new Cancel(); 

    synchronized (controller) { 
     ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> { 
      if (<CONTINUE RUNNING CONDITION) { 

      } else { 
       // STOP SCHEDULABLE FUTURE 
       controller.stop(); 
      } 
     }, startTime, timeBetweenVisbilityChecks); 
     controller.setFuture(future); 
    } 
} 

Así notar cómo la parada no será exigible hasta que el futuro ha sido creado y el futuro ha sido configurado en el controlador.

Tenga en cuenta que el Runnable es la clase interna anónima y esto se ejecutará en un hilo diferente por completo.

Cuestiones relacionadas