2012-08-01 18 views
8

Estoy tratando de crear un método que ejecute una tarea determinada en un tiempo máximo. Si no termina en ese momento, debe volver a intentarlo varias veces antes de darse por vencido. También debería esperar unos segundos entre cada intento. Esto es lo que se me ocurrió y me gustaría algunas críticas sobre mi enfoque. ¿Es una forma más sencilla de hacerlo utilizando el ScheduledExecutorService o es mi manera de hacer esto suficiente?Java ejecuta la tarea con un número de reintentos y un tiempo de espera

public static <T> T execute(Callable<T> task, int tries, int waitTimeSeconds, int timeout) 
    throws InterruptedException, TimeoutException, Exception { 

    Exception lastThrown = null; 
    for (int i = 0; i < tries; i++) { 
     try { 
      final Future<T> future = new FutureTask<T>(task); 
      return future.get(timeout, TimeUnit.SECONDS); 
     } catch (TimeoutException ex) { 
      lastThrown = ex; 
     } catch (ExecutionException ex) { 
      lastThrown = (Exception) ex.getCause(); 
     } 
     Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSeconds)); 
    } 
    if (lastThrown == null) { 
     lastThrown = new TimeoutException("Reached max tries without being caused by some exception. " + task.getClass()); 
    } 
    throw lastThrown; 
} 

Respuesta

5

creo, pero es mi opinión, que si está programando tareas relacionadas con la red, no se debe volver a intentar pero finalmente ejecutarlos en paralelo. Describo este otro enfoque más tarde.

En cuanto a su código, debe pasar la tarea a un ejecutor, o el FutureTask a un hilo. No engendrará un hilo ni se ejecutará solo. Si tiene un ejecutor (consulte ExecutorService), ni siquiera necesita un FutureTask, simplemente puede programarlo y obtener un llamador.

Por lo tanto, dado que tiene una ExecutorService, puede llamar:

Future<T> future = yourExecutor.submit(task); 

Future.get (tiempo de espera) esperará a que el tiempo de espera y, finalmente, volver con TimeoutException aunque la tarea no se ha iniciado en absoluto, por ejemplo, si el Ejecutor ya está ocupado haciendo otro trabajo y no puede encontrar un hilo libre. Por lo tanto, podrías terminar intentando 5 veces y esperando segundos sin dar nunca la oportunidad de ejecutar la tarea. Esto puede o no ser lo que espera, pero generalmente no lo es. Tal vez deberías esperar a que comience antes de darle un tiempo de espera.

Además, debe cancelar explícitamente el futuro aunque arroje TimeoutException, de lo contrario puede seguir ejecutándose, ya que ni la documentación ni el código indican que se detendrá cuando falle el tiempo de espera.

Incluso si lo cancela, a menos que el Callable se haya "escrito correctamente", podría seguir funcionando durante un tiempo. No hay nada que pueda hacer al respecto en esta parte del código, solo tenga en cuenta que ningún hilo puede "detener realmente" lo que otro hilo está haciendo en Java, y por buenas razones.

Sin embargo, supongo que sus tareas estarán principalmente relacionadas con la red, por lo que debería reaccionar correctamente a una interrupción de hilo.

que suelen utilizar una estrategia diferente es este tipo de situaciones:

  1. me gustaría escribir public static T execute (tarea que se puede llamar, int maxTries, int tiempo de espera), por lo que la tarea, el número máximo de intentos (potencialmente 1), máximo tiempo de espera total ("Quiero una respuesta en un máximo de 10 segundos, no importa cuántas veces lo intentes, 10 segundos o nada")
  2. Empiezo a engendrar la tarea, entregándola a un ejecutor y luego llamo a futuro. get (timeout/tries)
  3. Si recibo un resultado, devuélvalo. Si recibo una excepción, volveré a intentar (ver más adelante)
  4. Sin embargo, si recibo un tiempo de espera, NO cancelo el futuro, sino que lo guardo en una lista.
  5. Compruebo si ha pasado demasiado tiempo o demasiados reintentos. En ese caso cancelo todos los futuros en la lista y lanzo una excepción, devuelvo nulo, sea lo que sea
  6. De lo contrario, ciclo, programo la tarea nuevamente (en paralelo con la primera).
  7. Ver el punto 2
  8. Si no he recibido un resultado, verifico el/los futuro (es) en la lista, tal vez una de las tareas generadas previamente logró hacerlo.

Asumiendo que sus tareas se pueden ejecutar más de una vez (como supongo que son, de lo contrario no hay forma de volver a intentarlo), para las cosas de red encontré que esta solución funcionaba mejor.

Supongamos que su red está realmente muy ocupada, solicita una conexión de red, dando 20 intentos de 2 segundos cada uno. Como su red está ocupada, ninguno de los 20 intentos logra obtener la conexión en 2 segundos. Sin embargo, una única ejecución de 40 segundos puede lograr conectarse y recibir datos. Es como una persona presionando f5 compulsivamente en una página cuando la red es lenta, no servirá de nada, ya que cada vez que el navegador tiene que comenzar desde el principio.

En su lugar, mantengo los futuros en ejecución, el primero que logra obtener los datos devolverá un resultado y los demás se detendrán. Si el primero se cuelga, el segundo funcionará, o el tercero tal vez.

Comparando con un navegador, es como abrir otra pestaña y volver a intentar cargar la página allí sin cerrar la primera. Si la red es lenta, la segunda tardará un tiempo, pero no detendrá la primera, que eventualmente se cargará correctamente. Si, en cambio, se colgó la primera pestaña, la segunda se cargará rápidamente. Lo que ocurra primero, podemos cerrar la otra pestaña.

1

El hilo en el que se llamará su execute bloqueará durante tanto tiempo. No estoy seguro de si esto es correcto para usted. Básicamente, para este tipo de tareas, ScheduledExecutorService es lo mejor. Puede programar una tarea y especificar los tiempos. Eche un vistazo a ScheduledThreadPoolExecutor

Cuestiones relacionadas