web-dev-qa-db-fra.com

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

Existe-t-il une manière standard Nice d'appeler une méthode de blocage avec un timeout 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.

84
jjujuma

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 la future.get ne 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 JavaDoc pour plus de détails.

134
skaffman

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

Voir http://Java.Sun.com/j2se/1.5.0/docs/api/Java/util/concurrent/FutureTask.html

10
Colin Goudie

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 dépendre d'AspectJ et l'introduire dans votre cycle de vie de construction, bien sûr.

Il y a un article qui l'explique davantage: Limit Java Time Execution Method

3
gvlasov

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 essaient de placer l'appel de blocage dans un thread différent et ont un futur ou un minuteur. MAIS il n'y a aucun moyen dans Java pour 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.

Vous n'avez donc que 3 options génériques:

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

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

  3. Si votre appel de blocage n'est pas un E/S, mais votre logique, vous pouvez vérifier à plusieurs reprises Thread.isInterrupted() pour vérifier 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

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

Personnellement, j'essaie de programmer sans utiliser autant que possible les appels bloquants. Il existe par exemple des boîtes à outils comme Vert.x qui rendent les opérations IO et no IO [As IO $ ===) asynchrones et non bloquantes très faciles et performantes.

J'espère que ça aide

2
Michael P

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

2
Federico
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, une meilleure alternative est de définir un indicateur booléen volatile, à 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)");
        }
    }
}
1
jnr

Essaye ça. Solution plus simple. Garantit que si le bloc ne s'est pas exécuté dans le délai imparti. le processus se terminera et lève 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
    }
1

Je vous donne ici le code complet. Au lieu 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
        }
    }
}
1
VickyCool

Vous avez besoin d'une implémentation disjoncteur comme celle présente dans le projet failafe sur GitHub.

0
Marco Montel

Supposons que blockingMethod dors juste pour quelques millis:

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

Ma solution est d'utiliser wait() et synchronized comme 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();
        }
    }
}

Donc tu peux remplacer

something.blockingMethod(input)

à

something.blockingMethod(input, 2000)

J'espère que ça aide.

0
Euporie