80 votes

Java : définir un délai d'attente sur un certain bloc de code ?

Est-il possible de forcer Java à lancer une exception lorsqu'un bloc de code s'exécute plus longtemps que prévu ?

56voto

user2116890 Points 541

Voici le moyen le plus simple que je connaisse pour le faire :

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 l'utiliser partout où vous en avez besoin comme suit :

new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
    // Do stuff here.
}}.run();

0 votes

Je viens de tomber sur ça. Quels sont les frais généraux pour faire quelque chose de ce genre ? J'ai l'impression que si vous faites beaucoup de choses dans stuffToDo, la création d'un nouvel exécuteur à fil unique à chaque fois est coûteuse, mais je ne sais pas, d'où ma question.

0 votes

Je n'ai jamais rencontré de problème de performance avec cette approche. Dans certains cas, il peut être préférable de créer votre propre implémentation de la fonction Exécuteur testamentaire si vous pensez pouvoir créer une version plus légère en vous concentrant spécifiquement sur le cas d'un seul fil.

0 votes

Les performances de ExecutorService a été évalué favorablement ici ( stackoverflow.com/a/27025552/2116890 ), où il a été déterminé qu'elle était géniale. Dans ce cas, ils avaient affaire à un plus grand nombre de fils. Dans mon expérience, que ce soit avec un seul ou plusieurs threads, je n'ai jamais remarqué la surcharge, mais je n'ai pas non plus fait d'effort pour mesurer de quelque manière que ce soit ses performances par rapport à une alternative.

47voto

Neeme Praks Points 4150

J'ai compilé certaines des autres réponses en 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 faisant appel à 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);
  }

Résultat 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!

0 votes

Pouvez-vous ajouter un exemple d'utilisation ? Malgré mes réglages, l'exception est levée dès que j'exécute la fonction runWithTimeout même si je fixe le délai d'attente à 150 minutes.

2 votes

J'ai maintenant ajouté un exemple de code et amélioré le code original pour annuler la tâche déjà soumise en cas de timeout. J'apprécierais un upvote ;-)

0 votes

@htf, cela devrait vraiment être la réponse acceptée Bien fait

26voto

Peter Lawrey Points 229686

Oui, mais c'est généralement une très mauvaise idée de forcer un autre thread à s'interrompre sur une ligne de code aléatoire. Vous ne devriez le faire que si vous avez l'intention d'arrêter le processus.

Ce que vous pouvez faire est d'utiliser Thread.interrupt() pour une tâche après un certain temps. Cependant, à moins que le code ne vérifie ce point, cela ne fonctionnera pas. Un ExecutorService peut rendre cela plus facile avec Future.cancel(true)

C'est bien mieux pour le code de se chronométrer et de s'arrêter quand il le faut.

1 votes

Le problème est que j'ai une bibliothèque tierce qui s'exécute parfois trop longtemps et qu'il n'existe pas de mécanisme natif pour la temporisation.

2 votes

Il s'agit malheureusement d'un problème courant et la seule façon fiable de le gérer est d'avoir un processus séparé que vous pouvez tuer. L'alternative est d'utiliser Thread.stop() sur ce processus. Lisez les avertissements de cette méthode avant de l'utiliser !

10voto

mdma Points 33973

Si c'est du code de test que vous voulez chronométrer, alors vous pouvez utiliser la fonction 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 de la possibilité de modifier le code pour qu'il soit temporisé ou non.

Si vous pouvez modifier le code chronométré, une approche simple consiste à faire en sorte que votre code chronométré se souvienne de son heure de départ et à comparer périodiquement l'heure actuelle avec celle-ci. 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 pouvez exécuter le code sur un autre thread, et attendre l'achèvement, 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();
}

4voto

Farzin Zaker Points 2721

Je peux vous proposer deux options.

  1. Dans la méthode, en supposant qu'elle tourne en boucle et qu'elle n'attend pas un événement externe, ajoutez un champ local et testez le temps à chaque tour de boucle.

    void method() {
        long endTimeMillis = System.currentTimeMillis() + 10000;
        while (true) {
            // method logic
            if (System.currentTimeMillis() > endTimeMillis) {
                // do some clean-up
                return;
            }
        }
    }
  2. Exécutez la méthode dans un thread, et demandez à l'appelant de compter jusqu'à 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 la méthode() ne peut pas retourner une valeur directement, elle doit mettre à jour un champ d'instance pour retourner sa valeur.

1 votes

+0 : Dans votre deuxième exemple, essayez-vous de faire une Thread.join(long) avec un délai d'attente ;)

2 votes

Après thread.start() vous auriez pu thread.join(10000); au lieu du reste du code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X