Java: définir le délai d'attente sur un certain bloc de code?
Est-il possible de forcer Java à lancer une Exception après qu'un bloc de code s'exécute plus longtemps qu'acceptable?
10 réponses
Oui, mais c'est généralement une très mauvaise idée de forcer un autre thread à interrompre sur une ligne de code aléatoire. Vous ne faites cela que si vous avez l'intention d'arrêter le processus.
Ce que vous pouvez faire est Thread.interrupt()
une tâche sont un certain laps de temps. Cependant, à moins que le code ne vérifie cela, cela ne fonctionnera pas. Un ExecutorService peut rendre cela plus facile avec Future.cancel(true)
C'est beaucoup mieux pour le code de se chronométrer et de s'arrêter quand il le faut.
Voici la façon la plus simple que je connaisse de faire ceci:
final Runnable stuffToDo = new Thread() {
@Override
public void run() {
/* Do stuff here. */
}
};
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
future.get(5, TimeUnit.MINUTES);
}
catch (InterruptedException ie) {
/* Handle the interruption. Or ignore it. */
}
catch (ExecutionException ee) {
/* Handle the error. Or ignore it. */
}
catch (TimeoutException te) {
/* Handle the timeout. Or ignore it. */
}
if (!executor.isTerminated())
executor.shutdownNow(); // If you want to stop the code that hasn't finished.
Vous pouvez également créer une classe TimeLimitedCodeBlock pour envelopper cette fonctionnalité, puis vous pouvez l'utiliser partout où vous en avez besoin comme suit:
new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
// Do stuff here.
}}.run();
J'ai compilé certaines des autres réponses dans une seule méthode utilitaire:
public class TimeLimitedCodeBlock {
public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
runWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<T> future = executor.submit(callable);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
return future.get(timeout, timeUnit);
}
catch (TimeoutException e) {
//remove this if you do not want to cancel the job in progress
//or set the argument to 'false' if you do not want to interrupt the thread
future.cancel(true);
throw e;
}
catch (ExecutionException e) {
//unwrap the root cause
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
Exemple de code utilisant cette méthode utilitaire:
public static void main(String[] args) throws Exception {
final long startTime = System.currentTimeMillis();
log(startTime, "calling runWithTimeout!");
try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
@Override
public void run() {
try {
log(startTime, "starting sleep!");
Thread.sleep(10000);
log(startTime, "woke up!");
}
catch (InterruptedException e) {
log(startTime, "was interrupted!");
}
}
}, 5, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
log(startTime, "got timeout!");
}
log(startTime, "end of main method!");
}
private static void log(long startTime, String msg) {
long elapsedSeconds = (System.currentTimeMillis() - startTime);
System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
}
Sortie de l'exécution de l'exemple de code sur ma machine:
0ms [ main] calling runWithTimeout!
13ms [ pool-1-thread-1] starting sleep!
5015ms [ main] got timeout!
5016ms [ main] end of main method!
5015ms [ pool-1-thread-1] was interrupted!
Si c'est le code de test que vous voulez, alors vous pouvez utiliser le time
attribut:
@Test(timeout = 1000)
public void shouldTakeASecondOrLess()
{
}
S'il s'agit d'un code de production, il n'y a pas de mécanisme simple, et la solution que vous utilisez dépend si vous pouvez modifier le code à chronométrer ou non.
Si vous pouvez changer le code chronométré, alors une approche simple est d'avoir votre code chronométré se souvenir de son heure de début, et périodiquement l'heure actuelle par rapport à cela. Par exemple
long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
throw new RuntimeException("tiomeout");
Si le code lui-même ne peut pas vérifier le délai d'attente, vous peut exécuter le code sur un autre thread et attendre la fin ou le délai d'attente.
Callable<ResultType> run = new Callable<ResultType>()
{
@Override
public ResultType call() throws Exception
{
// your code to be timed
}
};
RunnableFuture future = new FutureTask(run);
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(future);
ResultType result = null;
try
{
result = future.get(1, TimeUnit.SECONDS); // wait 1 second
}
catch (TimeoutException ex)
{
// timed out. Try to stop the code if possible.
future.cancel(true);
}
service.shutdown();
}
EDIT: Peter Lawrey a complètement raison: ce n'est pas aussi simple que d'interrompre un thread (ma suggestion originale), et les exécuteurs et les Callables sont très utiles ...
Plutôt que d'interrompre les threads, vous pouvez définir une variable sur L'appelable une fois le délai d'attente atteint. L'appelable doit vérifier cette variable aux points appropriés de l'exécution de la tâche, pour savoir quand s'arrêter.
Les Callables renvoient des contrats à terme, avec lesquels vous pouvez spécifier un délai d'attente lorsque vous essayez d'obtenir le résultat du futur. Quelque chose comme ceci:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
Voir L'Avenir.obtenez, exécuteurs, et appelable ...
Https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html
Je peux suggérer deux options.
-
Dans la méthode, en supposant qu'elle boucle et n'attend pas un événement externe, ajoutez un champ local et testez l'heure à chaque fois autour de la boucle.
void method() { long endTimeMillis = System.currentTimeMillis() + 10000; while (true) { // method logic if (System.currentTimeMillis() > endTimeMillis) { // do some clean-up return; } } }
-
Exécutez la méthode dans un thread et faites Compter l'appelant à 10 Secondes.
Thread thread = new Thread(new Runnable() { @Override public void run() { method(); } }); thread.start(); long endTimeMillis = System.currentTimeMillis() + 10000; while (thread.isAlive()) { if (System.currentTimeMillis() > endTimeMillis) { // set an error flag break; } try { Thread.sleep(500); } catch (InterruptedException t) {} }
L'inconvénient de cette approche est que method () ne peut pas renvoyer une valeur directement, elle doit mettre à jour un champ d'instance pour renvoyer sa valeur.
J'ai créé une solution très simple sans utiliser de frameworks ou D'API. Cela semble plus élégant et compréhensible. La classe est appelée TimeoutBlock.
public class TimeoutBlock {
private final long timeoutMilliSeconds;
private long timeoutInteval=100;
public TimeoutBlock(long timeoutMilliSeconds){
this.timeoutMilliSeconds=timeoutMilliSeconds;
}
public void addBlock(Runnable runnable) throws Throwable{
long collectIntervals=0;
Thread timeoutWorker=new Thread(runnable);
timeoutWorker.start();
do{
if(collectIntervals>=this.timeoutMilliSeconds){
timeoutWorker.stop();
throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
}
collectIntervals+=timeoutInteval;
Thread.sleep(timeoutInteval);
}while(timeoutWorker.isAlive());
System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
}
/**
* @return the timeoutInteval
*/
public long getTimeoutInteval() {
return timeoutInteval;
}
/**
* @param timeoutInteval the timeoutInteval to set
*/
public void setTimeoutInteval(long timeoutInteval) {
this.timeoutInteval = timeoutInteval;
}
}
Exemple :
try {
TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
Runnable block=new Runnable() {
@Override
public void run() {
//TO DO write block of code to execute
}
};
timeoutBlock.addBlock(block);// execute the runnable block
} catch (Throwable e) {
//catch the exception here . Which is block didn't execute within the time limit
}
C'était tellement utile pour moi quand je devais me connecter à un compte FTP. Ensuite, téléchargez et téléchargez des choses. parfois, la connexion FTP se bloque ou se brise totalement. Cela a causé tout le système à descendre. et j'avais besoin d'un moyen de le détecter et de l'empêcher de se produire . J'ai donc créé ceci et l'ai utilisé. Travail plutôt bien.
Il y a une façon hacky de le faire.
Définissez un champ booléen pour indiquer si le travail a été terminé. Ensuite, avant le bloc de code, réglez une minuterie pour exécuter un morceau de code après votre délai d'attente. La minuterie vérifiera si le bloc de code a fini de s'exécuter, et sinon, lancera une exception. Sinon, il ne fera rien.
La fin du bloc de code devrait, bien sûr, définir le champ sur true pour indiquer que le travail a été effectué.
Vous devriez trouver le lien suivant utile pour gérer les exceptions de délai d'attente.
Http://www.javacoffeebreak.com/articles/network_timeouts/
Vous pouvez alors lancer une exception TimeoutException (voir http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/TimeoutException.html)
Au Lieu d'avoir la tâche dans le nouveau thread et de la minuterie dans le thread principal, ont la minuterie dans le nouveau thread et de la tâche dans le thread principal:
public static class TimeOut implements Runnable{
public void run() {
Thread.sleep(10000);
if(taskComplete ==false) {
System.out.println("Timed Out");
return;
}
else {
return;
}
}
}
public static boolean taskComplete = false;
public static void main(String[] args) {
TimeOut timeOut = new TimeOut();
Thread timeOutThread = new Thread(timeOutThread);
timeOutThread.start();
//task starts here
//task completed
taskComplete =true;
while(true) {//do all other stuff }
}