Wie rufe ich eine Blockierungsmethode mit einem Timeout in Java auf?

96

Gibt es eine gute Standardmethode, um eine Blockierungsmethode mit einem Timeout in Java aufzurufen? Ich möchte in der Lage sein:

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

wenn das Sinn macht.

Vielen Dank.

jjujuma
quelle
1
Lesen Sie als Referenz Java Concurrency in Practice von Brian Goetz, S. 126 - 134, insbesondere Abschnitt 6.3.7 "Festlegen von Zeitlimits für Aufgaben"
brown.2179

Antworten:

150

Sie könnten einen Executor verwenden:

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
}

Wenn das future.getnicht in 5 Sekunden zurückkehrt, wirft es a TimeoutException. Das Zeitlimit kann in Sekunden, Minuten, Millisekunden oder einer beliebigen Einheit konfiguriert werden, die als Konstante in verfügbar ist TimeUnit.

Weitere Informationen finden Sie im JavaDoc .

Skaffman
quelle
12
Die Blockierungsmethode wird auch nach dem Timeout weiter ausgeführt, oder?
Ivan Dubrov
1
Das hängt von der Zukunft ab. Abhängig davon, was die Blockierungsmethode gerade tut, kann sie beendet werden oder nicht.
Skaffman
4
Wie kann ich Parameter an blockingMethod () übergeben? Vielen Dank!
Robert A Henru
@RobertAHenru: Erstellen Sie eine neue Klasse mit dem Namen, BlockingMethodCallablederen Konstruktor die Parameter akzeptiert, an die Sie übergeben möchten, blockingMethod()und speichern Sie sie als Mitgliedsvariablen (wahrscheinlich als endgültig). Dann call()übergeben Sie diese Parameter an die blockMethod().
Vite Falcon
1
endlich sollte tun future.cancel(true)- Die Methode cancel (boolean) im Typ Future <Object> gilt nicht für die Argumente ()
Noam Manos
9

Sie können den Aufruf in a FutureTaskeinschließen und die Timeout-Version von get () verwenden.

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

Colin Goudie
quelle
1
FutureTask ist selbst nicht asynchron, oder? Allein erledigt es die Dinge nur synchron. Sie müssen es mit einem Executor kombinieren, um das asynchrone Verhalten zu verbessern.
Skaffman
6

Siehe auch Guavas TimeLimiter, der einen Executor hinter den Kulissen verwendet.

Federico
quelle
1
Gute Idee. Aber die Verbindung ist abgestanden. Googlecode ist nicht mehr .. versuchen Sie diesen Link zum SimpleTimeLimiter
Rhabarber
3

Dafür gibt es auch eine AspectJ-Lösung mit der jcabi-aspekte- Bibliothek.

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

Es kann nicht prägnanter werden, aber Sie müssen sich natürlich auf AspectJ verlassen und es in Ihren Build-Lebenszyklus einführen.

Es gibt einen Artikel, der dies weiter erklärt: Begrenzen Sie die Ausführungszeit der Java-Methode

gvlasov
quelle
3

Es ist wirklich großartig, dass die Leute versuchen, dies auf so viele Arten umzusetzen. Aber die Wahrheit ist, es gibt keinen Weg.

Die meisten Entwickler würden versuchen, den Blockierungsaufruf in einen anderen Thread zu stellen und eine Zukunft oder einen Timer zu haben. ABER es gibt in Java keine Möglichkeit, einen Thread extern zu stoppen, geschweige denn einige sehr spezifische Fälle wie die Methoden Thread.sleep () und Lock.lockInterruptibly (), die die Thread-Unterbrechung explizit behandeln.

Sie haben also wirklich nur 3 generische Optionen:

  1. Setzen Sie Ihren Blockierungsaufruf auf einen neuen Thread. Wenn die Zeit abgelaufen ist, fahren Sie einfach fort und lassen diesen Thread hängen. In diesem Fall sollten Sie sicherstellen, dass der Thread als Daemon-Thread festgelegt ist. Auf diese Weise verhindert der Thread nicht, dass Ihre Anwendung beendet wird.

  2. Verwenden Sie nicht blockierende Java-APIs. Verwenden Sie beispielsweise für das Netzwerk NIO2 und die nicht blockierenden Methoden. Verwenden Sie zum Lesen von der Konsole Scanner.hasNext (), bevor Sie blockieren usw.

  3. Wenn Ihr blockierender Aufruf kein E / A ist, sondern Ihre Logik, können Sie wiederholt prüfen Thread.isInterrupted(), ob er extern unterbrochen wurde, und einen weiteren Thread-Aufruf thread.interrupt()für den blockierenden Thread durchführen

Dieser Kurs über Parallelität https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

geht wirklich durch diese Grundlagen, wenn Sie wirklich verstehen wollen, wie es in Java funktioniert. In einer der Vorlesungen wird tatsächlich über diese spezifischen Einschränkungen und Szenarien gesprochen und wie man sie angeht.

Ich persönlich versuche zu programmieren, ohne so viele Anrufe wie möglich zu blockieren. Es gibt Toolkits wie Vert.x, die es wirklich einfach und performant machen, E / A- und keine E / A-Operationen asynchron und nicht blockierend auszuführen.

Ich hoffe, es hilft

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

Beachten Sie, dass stop veraltet ist. Eine bessere Alternative besteht darin, ein flüchtiges boolesches Flag zu setzen. Überprüfen Sie es in blockingMethod () und beenden Sie es wie folgt:

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
quelle
1

Versuche dies. Einfachere Lösung. Garantiert, dass der Block nicht innerhalb des Zeitlimits ausgeführt wurde. Der Prozess wird beendet und eine Ausnahme ausgelöst.

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

Beispiel:

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
quelle
1

Ich gebe Ihnen hier den vollständigen Code. Anstelle der von mir aufgerufenen Methode können Sie Ihre Methode verwenden:

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
quelle
0

Angenommen, Sie blockingMethodschlafen nur ein paar Millis:

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

Meine Lösung ist zu verwenden wait()und synchronizedwie folgt :

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

Also kannst du ersetzen

something.blockingMethod(input)

zu

something.blockingMethod(input, 2000)

Ich hoffe es hilft.

Euporie
quelle