Come posso chiamare un metodo di blocco con un timeout in Java?


97

C'è un bel modo standard per chiamare un metodo di blocco con un timeout in Java? Voglio essere in grado di fare:

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

se questo ha un senso.

Grazie.


1
Come riferimento, consulta Java Concurrency in Practice di Brian Goetz, pagine 126 - 134, in particolare la sezione 6.3.7 "Posizionamento dei limiti di tempo sulle attività"
marrone.2179

Risposte:


151

Potresti usare un Executor:

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
}

Se future.getnon ritorna entro 5 secondi, lancia un file TimeoutException. Il timeout può essere configurato in secondi, minuti, millisecondi o qualsiasi unità disponibile come costante in TimeUnit.

Vedi il JavaDoc per maggiori dettagli.


13
Il metodo di blocco continuerà a funzionare anche dopo il timeout, giusto?
Ivan Dubrov

1
Dipende da future.cancel. A seconda di cosa sta facendo il metodo di blocco in quel momento, potrebbe terminare o meno.
skaffman

4
come posso passare il parametro a blockingMethod ()? Grazie!
Robert A Henru

@RobertAHenru: crea una nuova classe chiamata il BlockingMethodCallablecui costruttore accetta i parametri a cui vuoi passare blockingMethod()e memorizzali come variabili membro (probabilmente come finali). Quindi call()passare questi parametri al file blockMethod().
Vite Falcon

1
finalmente dovrebbe fare future.cancel(true)- Il metodo cancel (booleano) nel tipo Future <Object> non è applicabile per gli argomenti ()
Noam Manos



3

C'è anche una soluzione AspectJ per quello con la libreria 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
}

Non può essere più succinto, ma devi dipendere da AspectJ e introdurlo nel ciclo di vita della build, ovviamente.

C'è un articolo che lo spiega ulteriormente: Limita il tempo di esecuzione del metodo Java


3

È davvero fantastico che le persone provino a implementarlo in così tanti modi. Ma la verità è che NON c'è modo.

La maggior parte degli sviluppatori proverebbe a mettere la chiamata di blocco in un thread diverso e avere un futuro o un timer. MA non c'è modo in Java di fermare un thread esternamente, per non parlare di alcuni casi molto specifici come i metodi Thread.sleep () e Lock.lockInterruptibly () che gestiscono esplicitamente l'interruzione del thread.

Quindi in realtà hai solo 3 opzioni generiche:

  1. Metti la tua chiamata di blocco su un nuovo thread e se il tempo scade vai avanti, lasciando quel thread in sospeso. In tal caso dovresti assicurarti che il thread sia impostato per essere un thread Daemon. In questo modo il thread non interromperà la chiusura dell'applicazione.

  2. Utilizza API Java non bloccanti. Quindi, per la rete, ad esempio, usa NIO2 e usa i metodi non bloccanti. Per leggere dalla console usa Scanner.hasNext () prima di bloccare ecc.

  3. Se la tua chiamata di blocco non è un IO, ma la tua logica, puoi controllare ripetutamente Thread.isInterrupted()se è stata interrotta esternamente e avere un'altra chiamata di thread thread.interrupt()sul thread di blocco

Questo corso sulla concorrenza https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

cammina davvero attraverso questi fondamenti se vuoi davvero capire come funziona in Java. In realtà parla di quei limiti e scenari specifici e di come affrontarli in una delle lezioni.

Personalmente cerco di programmare senza utilizzare il più possibile il blocco delle chiamate. Ci sono toolkit come Vert.x, ad esempio, che rendono davvero facile ed efficiente eseguire operazioni di I / O e nessuna operazione di I / O in modo asincrono e in modo non bloccante.

spero possa essere d'aiuto


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

Nota che lo stop è deprecato, un'alternativa migliore è impostare un flag booleano volatile, all'interno di blockingMethod () selezionalo ed esci, in questo modo:

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

Prova questo. Soluzione più semplice. Garantisce che se il blocco non è stato eseguito entro il limite di tempo. il processo terminerà e genererà un'eccezione.

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

esempio :

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

Ti sto dando qui il codice completo. Al posto del metodo che sto chiamando, puoi usare il tuo metodo:

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

0

Supponi di blockingMethoddormire solo per qualche millesimo:

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

La mia soluzione è usare wait()e in synchronizedquesto modo:

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

Quindi puoi sostituire

something.blockingMethod(input)

per

something.blockingMethod(input, 2000)

Spero che sia d'aiuto.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.