Comment appeler une méthode de blocage avec un délai d'expiration en Java?

97

Existe-t-il un moyen standard d'appeler une méthode de blocage avec un délai d'expiration en Java? Je veux pouvoir faire:

// call something.blockingMethod();
// if it hasn't come back within 2 seconds, forget it

si ça a du sens.

Merci.

jjujuma
la source
1
À titre de référence, consultez Java Concurrency in Practice de Brian Goetz p.
126-134

Réponses:

151

Vous pouvez utiliser un exécuteur:

ExecutorService executor = Executors.newCachedThreadPool();
Callable<Object> task = new Callable<Object>() {
   public Object call() {
      return something.blockingMethod();
   }
};
Future<Object> future = executor.submit(task);
try {
   Object result = future.get(5, TimeUnit.SECONDS); 
} catch (TimeoutException ex) {
   // handle the timeout
} catch (InterruptedException e) {
   // handle the interrupts
} catch (ExecutionException e) {
   // handle other exceptions
} finally {
   future.cancel(true); // may or may not desire this
}

Si le future.getne revient pas dans 5 secondes, il lance un TimeoutException. Le délai d'expiration peut être configuré en secondes, minutes, millisecondes ou toute unité disponible en tant que constante dans TimeUnit.

Voir le JavaDoc pour plus de détails.

skaffman
la source
13
La méthode de blocage continuera à s'exécuter même après le délai, non?
Ivan Dubrovnik
1
Cela dépend de future.cancel. En fonction de ce que fait la méthode de blocage à ce moment-là, elle peut ou non se terminer.
skaffman
4
comment puis-je passer un paramètre à blockingMethod ()? Merci!
Robert A Henru
@RobertAHenru: Créez une nouvelle classe appelée BlockingMethodCallabledont le constructeur accepte les paramètres que vous souhaitez passer blockingMethod()et stockez-les en tant que variables membres (probablement comme finales). Ensuite, call()passez ces paramètres à l' intérieur blockMethod().
Vite Falcon
1
enfin devrait faire future.cancel(true)- La méthode cancel (boolean) dans le type Future <Object> n'est pas applicable pour les arguments ()
Noam Manos
9

Vous pouvez encapsuler l'appel dans un FutureTasket utiliser la version timeout de get ().

Voir http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/FutureTask.html

Colin Goudie
la source
1
FutureTask n'est pas lui-même asynchrone, n'est-ce pas? À lui seul, il fait les choses de manière synchrone, vous devez le combiner avec un exécuteur pour créer un comportement asynchrone.
skaffman
6

Voir aussi TimeLimiter de Guava qui utilise un exécuteur dans les coulisses.

Federico
la source
1
Bonne idée. Mais le lien est périmé. Googlecode n'est plus .. essayez ce lien vers le SimpleTimeLimiter
Rhubarb
3

Il existe également une solution AspectJ pour cela avec la bibliothèque jcabi-aspects .

@Timeable(limit = 30, unit = TimeUnit.MINUTES)
public Soup cookSoup() {
  // Cook soup, but for no more than 30 minutes (throw and exception if it takes any longer
}

Cela ne peut pas être plus succinct, mais vous devez compter sur AspectJ et l'introduire dans votre cycle de vie de construction, bien sûr.

Il y a un article qui l'explique plus en détail: Limiter le temps d'exécution de la méthode Java

gvlasov
la source
3

C'est vraiment génial que les gens essaient de mettre en œuvre cela de tant de façons. Mais la vérité est qu'il n'y a aucun moyen.

La plupart des développeurs essaieraient de placer l'appel de blocage dans un thread différent et d'avoir un avenir ou une minuterie. MAIS il n'y a aucun moyen en Java d'arrêter un thread en externe, sans parler de quelques cas très spécifiques comme les méthodes Thread.sleep () et Lock.lockInterruptably () qui gèrent explicitement l'interruption de thread.

Donc, vraiment, vous n'avez que 3 options génériques:

  1. Mettez votre appel de blocage sur un nouveau thread et si le temps expire, vous passez simplement à autre chose, laissant ce fil suspendu. Dans ce cas, vous devez vous assurer que le thread est défini pour être un thread Daemon. De cette façon, le thread n'empêchera pas votre application de se terminer.

  2. Utilisez des API Java non bloquantes. Donc pour le réseau par exemple, utilisez NIO2 et utilisez les méthodes non bloquantes. Pour lire depuis la console, utilisez Scanner.hasNext () avant de bloquer, etc.

  3. Si votre appel de blocage n'est pas un IO, mais votre logique, vous pouvez vérifier à plusieurs reprises Thread.isInterrupted()s'il a été interrompu en externe et avoir un autre appel de thread thread.interrupt()sur le thread de blocage.

Ce cours sur la concurrence https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

décrit vraiment ces principes fondamentaux si vous voulez vraiment comprendre comment cela fonctionne en Java. Il parle en fait de ces limites et scénarios spécifiques, et de la façon de les aborder dans l'une des conférences.

J'essaye personnellement de programmer sans utiliser autant que possible le blocage des appels. Il existe des boîtes à outils comme Vert.x par exemple qui permettent d'effectuer très facilement et efficacement des opérations d'E / S et aucune opération d'E / S de manière asynchrone et de manière non bloquante.

J'espère que ça aide

Michael P
la source
1
Thread thread = new Thread(new Runnable() {
    public void run() {
        something.blockingMethod();
    }
});
thread.start();
thread.join(2000);
if (thread.isAlive()) {
    thread.stop();
}

Notez que stop est obsolète, la meilleure alternative est de définir un indicateur booléen volatil, à l'intérieur de blockingMethod (), vérifiez-le et quittez, comme ceci:

import org.junit.*;
import java.util.*;
import junit.framework.TestCase;

public class ThreadTest extends TestCase {
    static class Something implements Runnable {
        private volatile boolean stopRequested;
        private final int steps;
        private final long waitPerStep;

        public Something(int steps, long waitPerStep) {
            this.steps = steps;
            this.waitPerStep = waitPerStep;
        }

        @Override
        public void run() {
            blockingMethod();
        }

        public void blockingMethod() {
            try {
                for (int i = 0; i < steps && !stopRequested; i++) {
                    doALittleBit();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        public void doALittleBit() throws InterruptedException {
            Thread.sleep(waitPerStep);
        }

        public void setStopRequested(boolean stopRequested) {
            this.stopRequested = stopRequested;
        }
    }

    @Test
    public void test() throws InterruptedException {
        final Something somethingRunnable = new Something(5, 1000);
        Thread thread = new Thread(somethingRunnable);
        thread.start();
        thread.join(2000);
        if (thread.isAlive()) {
            somethingRunnable.setStopRequested(true);
            thread.join(2000);
            assertFalse(thread.isAlive());
        } else {
            fail("Exptected to be alive (5 * 1000 > 2000)");
        }
    }
}
jnr
la source
1

Essaye ça. Solution plus simple. Garantit que si le blocage ne s'est pas exécuté dans le délai imparti. le processus se terminera et lèvera une exception.

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 
            }
        };

        timeoutBlock.addBlock(block);// execute the runnable block 

    } catch (Throwable e) {
        //catch the exception here . Which is block didn't execute within the time limit
    }
Niroshan Abeywickrama
la source
1

Je vous donne ici le code complet. À la place de la méthode que j'appelle, vous pouvez utiliser votre méthode:

public class NewTimeout {
    public String simpleMethod() {
        return "simple method";
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        Callable<Object> task = new Callable<Object>() {
            public Object call() throws InterruptedException {
                Thread.sleep(1100);
                return new NewTimeout().simpleMethod();
            }
        };
        Future<Object> future = executor.submit(task);
        try {
            Object result = future.get(1, TimeUnit.SECONDS); 
            System.out.println(result);
        } catch (TimeoutException ex) {
            System.out.println("Timeout............Timeout...........");
        } catch (InterruptedException e) {
            // handle the interrupts
        } catch (ExecutionException e) {
            // handle other exceptions
        } finally {
            executor.shutdown(); // may or may not desire this
        }
    }
}
VickyCool
la source
0

Supposons blockingMethodjuste dormir pendant quelques millis:

public void blockingMethod(Object input) {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Ma solution est d'utiliser wait()et synchronizedcomme ceci:

public void blockingMethod(final Object input, long millis) {
    final Object lock = new Object();
    new Thread(new Runnable() {

        @Override
        public void run() {
            blockingMethod(input);
            synchronized (lock) {
                lock.notify();
            }
        }
    }).start();
    synchronized (lock) {
        try {
            // Wait for specific millis and release the lock.
            // If blockingMethod is done during waiting time, it will wake
            // me up and give me the lock, and I will finish directly.
            // Otherwise, when the waiting time is over and the
            // blockingMethod is still
            // running, I will reacquire the lock and finish.
            lock.wait(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Alors tu peux remplacer

something.blockingMethod(input)

à

something.blockingMethod(input, 2000)

J'espère que ça aide.

Euporie
la source