2008-10-27 9 views
15

Tengo un método al que me gustaría llamar. Sin embargo, estoy buscando una manera limpia y simple de matarlo o forzarlo a regresar si tarda demasiado en ejecutarse.¿Cómo puedo ajustar un método para que pueda matar su ejecución si excede un tiempo de espera especificado?

Estoy usando Java.

para ilustrar:

logger.info("sequentially executing all batches..."); 
for (TestExecutor executor : builder.getExecutors()) { 
logger.info("executing batch..."); 
executor.execute(); 
} 

I figura la clase TestExecutor debe implement Callable y continuar en esa dirección.

Pero todo lo que quiero poder hacer es detener executor.execute() si está tardando demasiado.

Sugerencias ...?

EDITAR

Muchas de las sugerencias recibidas a suponer que el método en ejecución que lleva mucho tiempo contiene una especie de bucle y que una variable periódicamente se pudo comprobar. Sin embargo, este no es el caso. Entonces, algo que no necesariamente estará limpio y que simplemente detendrá la ejecución donde sea es aceptable.

Respuesta

7

Estoy asumiendo el uso de múltiples hilos en las siguientes afirmaciones.

He leído algo en esta área y la mayoría de los autores dicen que es una mala idea matar a otro hilo.

Si la función que desea eliminar puede diseñarse para comprobar periódicamente una variable o primitiva de sincronización, y luego terminar limpiamente si esa variable o primitiva de sincronización está configurada, eso sería bastante limpio. Entonces, algún tipo de subproceso de monitor puede dormir durante varios milisegundos y luego establecer la variable o primitiva de sincronización.

+0

gracias por su respuesta. en este caso, la función no puede diseñarse para verificar periódicamente una variable o primitiva de sincronización. – carrier

3

Realmente, no se puede ... La única manera de hacerlo es usar thread.stop, acordar un método "cooperativo" (por ejemplo, comprobar ocasionalmente si Thread.isinterrupted o llamar a un método que arroja una interruptedException , por ejemplo, Thread.sleep()), o de alguna manera invocar el método en otra JVM por completo.

Para ciertos tipos de pruebas, llamar a stop() está bien, pero probablemente dañará el estado de su conjunto de pruebas, por lo que tendrá que reiniciar la JVM después de cada llamada para detener() si desea evitar efectos de interacción.

Para obtener una buena descripción de cómo implementar el enfoque cooperativo, consulte Sun's FAQ on the deprecated Thread methods.

Para un ejemplo de este enfoque en la vida real, el objeto 'IProgressMonitor' Eclipse RCP's Job API's permite que algunos servicios de administración señalicen los subprocesos (a través del método 'cancelar') que deben detenerse. Por supuesto, eso depende de los métodos para verificar realmente el método isCancelled con regularidad, que a menudo no pueden hacer.

Un enfoque híbrido podría ser preguntarle al hilo muy bien con la interrupción, luego insistir un par de segundos más tarde con la parada. De nuevo, no debería usar detenerse en el código de producción, pero podría estar bien en este caso, esp. si sale de la JVM poco después.

Para probar este enfoque, escribí un arnés simple, que toma un ejecutable e intenta ejecutarlo. Siéntase libre de comentar/editar.

public void testStop(Runnable r) { 
    Thread t = new Thread(r); 
    t.start(); 
    try { 
     t.join(2000); 
    } catch (InterruptedException e) { 
     throw new RuntimeException(e); 
    } 

    if (!t.isAlive()) { 
     System.err.println("Finished on time."); 
     return; 
    } 

    try { 
     t.interrupt(); 
     t.join(2000); 
     if (!t.isAlive()) { 
      System.err.println("cooperative stop"); 
      return; 
     } 
    } catch (InterruptedException e) { 
     throw new RuntimeException(e); 
    } 
    System.err.println("non-cooperative stop"); 
    StackTraceElement[] trace = Thread.getAllStackTraces().get(t); 
    if (null != trace) { 
     Throwable temp = new Throwable(); 
     temp.setStackTrace(trace); 
     temp.printStackTrace(); 
    } 
    t.stop(); 
    System.err.println("stopped non-cooperative thread"); 
} 

Para probarlo, me escribió dos compiten bucles infinitos, una cooperativa, y uno que nunca se comprueba poco interrumpida de su hilo.

public void cooperative() { 
    try { 
     for (;;) { 
      Thread.sleep(500); 
     } 
    } catch (InterruptedException e) { 
     System.err.println("cooperative() interrupted"); 
    } finally { 
     System.err.println("cooperative() finally"); 
    } 
} 

public void noncooperative() { 
    try { 
     for (;;) { 
      Thread.yield(); 
     } 
    } finally { 
     System.err.println("noncooperative() finally"); 
    } 
} 

Finalmente, escribí las pruebas (JUnit 4) ejercer ellos:

@Test 
public void testStopCooperative() { 
    testStop(new Runnable() { 
     @Override 
     public void run() { 
      cooperative(); 
     } 
    }); 
} 

@Test 
public void testStopNoncooperative() { 
    testStop(new Runnable() { 
     @Override 
     public void run() { 
      noncooperative(); 
     } 
    }); 
} 

que nunca había utilizado Thread.stop() antes, así que no estaba al tanto de su funcionamiento. Funciona lanzando un objeto ThreadDeath desde donde se está ejecutando actualmente el hilo objetivo. Esto extiende el error. Entonces, si bien no siempre funciona limpiamente, generalmente dejará programas simples con un estado de programa bastante razonable. Por ejemplo, se llama a cualquier bloque final. Si quisieras ser un verdadero imbécil, podrías capturar ThreadDeath (o Error), ¡y seguir funcionando!

Por lo menos, esto realmente me hace desear más código siguió el enfoque IProgressMonitor - agregando otro parámetro a los métodos que podría tardar un rato, y fomentar el implementador del método para sondear vez en cuando el objeto de monitor para ver si el el usuario quiere que el sistema se rinda. Trataré de seguir este patrón en el futuro, especialmente los métodos que podrían ser interactivos. Por supuesto, no necesariamente sabes de antemano qué métodos se utilizarán de esta manera, pero para eso son los Profilers, supongo.

En cuanto al método 'iniciar otra JVM por completo', eso requerirá más trabajo. No sé si alguien ha escrito un cargador de clase delegante, o si uno está incluido en la JVM, pero eso sería necesario para este enfoque.

8

Java interruption mechanism es para este tipo de escenario. Si el método que desea abortar es ejecutar un bucle, simplemente haga que verifique el hilo interrupted status en cada iteración. Si se interrumpe, lanza una InterruptedException.

Luego, cuando desee cancelar, solo tiene que invocar interrupt en el hilo apropiado.

Alternativamente, puede utilizar el approach Sun como una alternativa al método de parada en desuso. Esto no implica arrojar ninguna excepción, el método simplemente volvería normalmente.

+0

lamentablemente no hay bucle. – carrier

1

Nadie respondió directamente, así que aquí es lo más parecido que pueda darle en un corto período de pseudo código:

enrolle el método en un ejecutable/exigible. El método en sí tendrá que verificar si hay un estado interrumpido si desea que se detenga (por ejemplo, si este método es un ciclo, dentro de la comprobación de bucle para Thread.currentThread(). IsInterrupted y, si es así, detenga el ciclo (don Sin embargo, compruebe cada iteración, o simplemente disminuya la velocidad. en el método de envoltura, use thread.join (timeout) para esperar el momento en que desee que el método se ejecute o, dentro de un bucle, invoque únase repetidamente con un tiempo de espera menor si necesita hacer otras cosas mientras espera. Si el método no finaliza, después de unirse, use las recomendaciones anteriores para abortar rápido/limpio.

lo que el código sabia, viejo código:

void myMethod() 
{ 
    methodTakingAllTheTime(); 
} 

nuevo código:

void myMethod() 
{ 
    Thread t = new Thread(new Runnable() 
     { 
      public void run() 
      { 
       methodTakingAllTheTime(); // modify the internals of this method to check for interruption 
      } 
     }); 
    t.join(5000); // 5 seconds 
    t.interrupt(); 
} 

pero de nuevo, para que esto funcione bien, usted todavía tiene que modificar o methodTakingAllTheTime ese hilo se acaba continuar corriendo después de haber llamado a interrupción.

+0

En realidad, no alcanzará t.interrupt(). Thread.join lo hará a través de una excepción interrumpida, y t.interrupt será pasado por alto. En realidad, dado que InterruptedException está marcado, lo anterior no se compilará. Hmmm ... Debería controlarme antes de destrozarme ... – krakatoa

+0

Estaba equivocado sobre el uso de thread.join de interrumptedException. Simplemente continuará. Entonces su código es correcto, modulo la declaración de tiro faltante. – krakatoa

11

Usted debe echar un vistazo a estas clases: FutureTask, Callable, Executors

He aquí un ejemplo:

public class TimeoutExample { 
    public static Object myMethod() { 
     // does your thing and taking a long time to execute 
     return someResult; 
    } 

    public static void main(final String[] args) { 
     Callable<Object> callable = new Callable<Object>() { 
      public Object call() throws Exception { 
       return myMethod(); 
      } 
     }; 
     ExecutorService executorService = Executors.newCachedThreadPool(); 

     Future<Object> task = executorService.submit(callable); 
     try { 
      // ok, wait for 30 seconds max 
      Object result = task.get(30, TimeUnit.SECONDS); 
      System.out.println("Finished with result: " + result); 
     } catch (ExecutionException e) { 
      throw new RuntimeException(e); 
     } catch (TimeoutException e) { 
      System.out.println("timeout..."); 
     } catch (InterruptedException e) { 
      System.out.println("interrupted"); 
     } 
    } 
} 
+3

Lo único que agregaría es que 'finally {executorService .shutdown(); } 'para hacer un poco de limpieza. – Greg

0

La respuesta correcta es, creo, para crear un Ejecutable para ejecutar la subprograma, y ​​ejecutar esto en un hilo separado. El Runnable puede ser un FutureTask, que se puede ejecutar con un tiempo de espera (método "get"). Si el tiempo de espera, obtendrá un TimeoutException, en la que le sugiero

  • thread.interrupt llamada() para intentar acabar con ella de una manera semi-cooperativo (muchas llamadas a las bibliotecas parecen ser sensibles a este , entonces probablemente funcione)
  • espere un poco (Thread.sleep (300))
  • y luego, si el hilo todavía está activo (thread.isActive()), llame a thread.stop(). Este es un método obsoleto, pero aparentemente es el único juego en la ciudad excepto ejecutar un proceso separado con todo lo que esto conlleva.

En mi aplicación, donde ejecuto un código no cooperativo y no cooperativo escrito por mis estudiantes principiantes, hago lo anterior, asegurándome de que el hilo asesinado nunca tiene acceso (escritura) a ningún objeto que sobreviva a su muerte. Esto incluye el objeto que alberga el método llamado, que se descarta si se produce un tiempo de espera. (Les digo a mis alumnos que eviten los tiempos muertos, porque su agente será descalificado.) No estoy seguro de fugas de memoria ...

Distingo entre los tiempos de ejecución largos (el método termina) y los tiempos muertos difíciles: los tiempos muertos son más largos y significados para atrapar el caso cuando el código no termina en absoluto, en lugar de ser lento.

Según mi investigación, Java no parece tener una disposición no obsoleta para ejecutar código no cooperativo, que, de alguna manera, es un gran vacío en el modelo de seguridad. O bien puedo ejecutar código extranjero y controlar los permisos que tiene (SecurityManager), o no puedo ejecutar código extraño, porque podría acabar ocupando toda una CPU sin medios no desaprovechados para detenerlo.

double x = 2.0; 
while(true) {x = x*x}; // do not terminate 
System.out.print(x); // prevent optimization 
0

No puedo pensar en una forma no tan buena de hacer esto. Si puede detectar cuándo tarda demasiado tiempo, puede hacer que el método compruebe si hay un valor booleano en cada paso. Haga que el programa cambie el valor de boolean tooMuchTime a true si le toma demasiado tiempo (no puedo ayudar con esto). A continuación, utilice algo como esto:

Method(){ 
//task1 
if (tooMuchTime == true) return; 
//task2 
if (tooMuchTime == true) return; 
//task3 
if (tooMuchTime == true) return; 
//task4 
if (tooMuchTime == true) return; 
//task5 
if (tooMuchTime == true) return; 
//final task 
    } 
Cuestiones relacionadas