Jak przekroczyć limit wątku

255

Chcę uruchomić wątek przez określony czas. Jeśli nie zostanie ukończony w tym czasie, chcę go zabić, zgłosić wyjątek lub jakoś sobie z nim poradzić. Jak można to zrobić?

Jednym ze sposobów zrobienia tego, jak się zorientowałem tym wątku, jest użycie TimerTask wewnątrz metody run () wątku.

Czy są na to jakieś lepsze rozwiązania?

 
EDYCJA: Dodanie nagrody, ponieważ potrzebowałem jaśniejszej odpowiedzi. Podany poniżej kod ExecutorService nie rozwiązuje mojego problemu. Dlaczego powinienem spać () po uruchomieniu (trochę kodu - nie mam żadnej kontroli nad tym fragmentem kodu)? Jeśli kod zostanie zakończony, a funkcja sleep () zostanie przerwana, jak może to być timeOut?

Zadanie, które należy wykonać, nie jest pod moją kontrolą. Może to być dowolny fragment kodu. Problem polega na tym, że ten fragment kodu może działać w nieskończonej pętli. Nie chcę, żeby tak się stało. Chcę tylko uruchomić to zadanie w osobnym wątku. Wątek nadrzędny musi poczekać, aż ten wątek się zakończy i musi znać status zadania (tj. Czy upłynął limit czasu lub wystąpił jakiś wyjątek, czy też powodzenie). Jeśli zadanie przejdzie w nieskończoną pętlę, mój wątek nadrzędny będzie czekał w nieskończoność, co nie jest idealną sytuacją.

java_geek
źródło
EDYCJA: Dodanie nagrody, ponieważ potrzebowałem bardziej jasnej odpowiedzi. podany poniżej kod ExecutorService nie rozwiązuje mojego problemu. Dlaczego powinienem spać () po wykonaniu mojego kodu? Jeśli kod zostanie zakończony, a funkcja sleep () zostanie przerwana, jak może to być timeOut?
java_geek
7
To sleep()był tylko skrót do reprezentowania „długiego zadania”. Po prostu zastąp go swoim prawdziwym zadaniem;)
BalusC
1
... „długo działające zadanie”, które zdarza się odpowiadać na interrupt()wywołania w jego wątku ... nie wszystkie wywołania „blokujące” robią, jak starałem się wskazać w mojej odpowiedzi. Specyfika zadania, które próbujesz przerwać, ma ogromną różnicę w podejściu, które należy zastosować. Przydałoby się więcej informacji o zadaniu.
erickson
Jeśli te odpowiedzi nie rozwiązują problemu, to chyba więcej szczegółów / kodu powinno pomóc w udzieleniu odpowiedzi.
Elister
Te wątki, które chcesz ograniczyć czasowo; czy wykonują blokujące połączenia, czy też znajdują się w pętli, w której można łatwo sprawdzić zmienną, aby sprawdzić, czy czas wyjść?
Scott Smith

Odpowiedzi:

376

Rzeczywiście raczej użyj ExecutorServicezamiast Timer, oto SSCCE :

package com.stackoverflow.q2275443;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Test {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Task());

        try {
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            future.cancel(true);
            System.out.println("Terminated!");
        }

        executor.shutdownNow();
    }
}

class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(4000); // Just to demo a long running task of 4 seconds.
        return "Ready!";
    }
}

Zagraj trochę timeoutargumentem w Future#get()metodzie, np. Zwiększ go do 5, a zobaczysz, że wątek się kończy. Możesz przechwycić limit czasu w catch (TimeoutException e)bloku.

Aktualizacja: aby wyjaśnić nieporozumienie pojęciowe, niesleep() jest wymagane. Jest po prostu używany do celów SSCCE / demonstracyjnych. Po prostu wykonaj swoje długofalowe zadanie w miejscu . W ramach długiego zadania powinieneś sprawdzić, czy wątek nie został przerwany w następujący sposób:sleep()

while (!Thread.interrupted()) {
    // Do your long running task here.
}
BalusC
źródło
24
Zamień na Thread.sleep(4000)inną długo działającą instrukcję, a przykład nie zadziała. Innymi słowy, ten przykład działałby tylko wtedy, gdy Taskzostał zaprojektowany w celu zrozumienia Thread.isInterrupted()zmiany statusu.
yegor256
@BalusC Próbowałem tego podejścia, próbując zakończyć moje wątki, ale nie mogłem go uruchomić. Możesz to sprawdzić tutaj: stackoverflow.com/questions/35553420/…
syfantid
Jak obsługiwany jest wyjątek InterruptedException w pliku future.cancel (true)?
bolei
1
n osób skomentowało nazwę pakietu, a oto jeszcze jeden +1 za to. To dobra umiejętność do przyswojenia. Dzięki!
Ashwin Tumma,
@BalusC Mam wątpliwości, czy Przyszłość wykona się synchronicznie i jeśli zajmie to więcej niż z góry określony czas, zostanie ona zakończona. W przeciwnym razie będzie to miało miejsce w przyszłości, tymczasem liczymy na czas ... Dziękuję
Adeel Ahmad
49

Nie ma w 100% niezawodnego sposobu na zrobienie tego dla każdego starego zadania. Zadanie należy napisać z myślą o tej zdolności.

Podstawowe biblioteki Java, takie jak ExecutorServiceanuluj zadania asynchroniczne z interrupt()wywołaniami w wątku roboczym. Na przykład, jeśli zadanie zawiera jakąś pętlę, powinieneś sprawdzać status przerwania na każdej iteracji. Jeśli zadaniem jest wykonywanie operacji we / wy, powinny one również być przerywane - a konfiguracja może być trudna. W każdym razie pamiętaj, że kod musi aktywnie sprawdzać przerwania; ustawienie przerwania niekoniecznie nic nie robi.

Oczywiście, jeśli twoim zadaniem jest jakaś prosta pętla, możesz po prostu sprawdzić aktualny czas na każdej iteracji i zrezygnować po upływie określonego czasu. W takim przypadku wątek roboczy nie jest potrzebny.

erickson
źródło
Z mojego doświadczenia wynika, że ​​jedynym kodem, który nie reaguje na rozpoczęcie przerwania, jest blokowanie kodu natywnego (oczekiwanie na system operacyjny).
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Zgadzam się, ale to dużo kodu. Chodzi mi o to, że nie ma w tym celu mechanizmu ogólnego przeznaczenia; musisz zrozumieć politykę przerwania zadania.
erickson
@erickson, zgadzam się z twoim. Jeśli chodzi o odpowiedź punktową, dla każdego zadania należy zdefiniować zasady anulowania, jeśli chcesz je w ten sposób zatrzymać. Lub wątek powinien być świadomy tego, co powinien zrobić, gdy zostanie przerwany. Przerwanie i zatrzymanie dowolnego wątku jest po prostu żądaniem, które docelowy wątek może zaakceptować lub odrzucić, więc lepiej napisać zadanie, mając to na uwadze.
AKS
Nie możesz wykonać executorservice, aby uruchomić zadanie w wątku wywołującym? również executorservice może zdecydować się na wykonanie zadania w przyszłości?
filthy_wizard
@ user1232726 execute()Metoda interfejsu nadrzędnego Executormoże uruchomić zadanie w wątku wywołującym. Nie ma podobnego stwierdzenia dla submit()metod ExecutorServicetych zwracanych Futureinstancji. Implikacja usługi polega na tym, że istnieją wątki robocze, które muszą zostać wyczyszczone przez zamknięcie, a zadania są wykonywane asynchronicznie. To powiedziawszy, w umowie nie ma nic, co mówi, że ExecutorServicezabrania się wykonywania zadań w wątku zgłaszającym; gwarancje te pochodzą z interfejsów API implementacji, takich jak Executorsfabryki.
erickson,
13

Rozważ użycie instancji klasy ExecutorService . Zarówno invokeAll()i invokeAny()są dostępne z metodytimeout parametru.

Bieżący wątek będzie blokowany do czasu zakończenia metody (nie jestem pewien, czy jest to pożądane), ponieważ zadanie (zadania) zakończyło się normalnie lub upłynął limit czasu. Możesz sprawdzić zwrócony Future(e) w celu ustalenia, co się stało.

Drew Wills
źródło
9

Zakładając, że kod wątku jest poza twoją kontrolą:

Z dokumentacji Java wspomnianej powyżej:

Co jeśli wątek nie odpowiada na Thread.interrupt?

W niektórych przypadkach możesz użyć sztuczek specyficznych dla aplikacji. Na przykład, jeśli wątek czeka na znanym gnieździe, możesz go zamknąć, aby natychmiast powrócił. Niestety, tak naprawdę żadna technika nie działa w ogóle. Należy zauważyć, że we wszystkich sytuacjach, w których oczekujący wątek nie odpowiada na Thread.interrupt, nie reaguje również na Thread.stop. Takie przypadki obejmują celowe ataki typu „odmowa usługi” oraz operacje we / wy, dla których wątki.stop i nici.przerwanie nie działają poprawnie.

Dolna linia:

Upewnij się, że wszystkie wątki mogą zostać przerwane, w przeciwnym razie potrzebujesz konkretnej wiedzy na temat wątku - na przykład posiadania flagi do ustawienia. Być może możesz wymagać od ciebie zadania wraz z kodem potrzebnym do jego zatrzymania - zdefiniuj interfejs za pomocą stop()metody. Możesz także ostrzec, gdy nie udało Ci się zatrzymać zadania.

Peter Tseng
źródło
8

BalusC powiedział:

Aktualizacja: aby wyjaśnić nieporozumienie pojęciowe, sleep () nie jest wymagane. Jest po prostu używany do celów SSCCE / demonstracyjnych. Po prostu wykonaj swoje długo działające zadanie w miejscu sleep ().

Ale jeśli zastąpi Thread.sleep(4000);się for (int i = 0; i < 5E8; i++) {}to nie skompilować, ponieważ pusta pętla nie rzucaćInterruptedException .

Aby nić mogła zostać przerwana, musi rzucić InterruptedException .

Wydaje mi się to poważnym problemem. Nie widzę, jak dostosować tę odpowiedź do pracy z ogólnym długoterminowym zadaniem.

Edytowano, aby dodać: Ponownie zadałem to pytanie: [ przerywając wątek po ustalonym czasie, czy musi zgłosić wyjątek InterruptedException? ]

użytkownik1310503
źródło
Sposób, w jaki to robię, polega na dodaniu „wyjątku dotyczącego rzutów” w publicznej metodzie klasy <T> wywołanie {}
Roberto Linares,
5

Myślę, że powinieneś przyjrzeć się odpowiednim mechanizmom obsługi współbieżności (wątki działające w nieskończonych pętlach same w sobie nie brzmią dobrze, btw). Przeczytaj trochę o temacie „zabijanie” lub „zatrzymywanie” wątków .

To, co opisujesz , brzmi jak „spotkanie”, więc możesz rzucić okiem na CyclicBarrier .

Mogą istnieć inne konstrukcje (np. Użycie CountDownLatch na przykład ), które mogą rozwiązać twój problem (jeden wątek czeka z limitem czasu na zatrzask, drugi powinien odliczać zatrzask, jeśli wykonał swoją pracę, co zwolniłoby twój pierwszy wątek albo po limit czasu lub wywołanie odliczania zatrzasku).

Zazwyczaj polecam dwie książki z tego zakresu: Programowanie współbieżne w Javie i Współbieżność Java w praktyce .

Dieter
źródło
5

Właśnie temu stworzyłem klasę pomocników. Działa świetnie:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
 * TimeOut class - used for stopping a thread that is taking too long
 * @author Peter Goransson
 *
 */
public class TimeOut {

    Thread interrupter;
    Thread target;
    long timeout;
    boolean success;
    boolean forceStop;

    CyclicBarrier barrier;

    /**
     * 
     * @param target The Runnable target to be executed
     * @param timeout The time in milliseconds before target will be interrupted or stopped
     * @param forceStop If true, will Thread.stop() this target instead of just interrupt() 
     */
    public TimeOut(Runnable target, long timeout, boolean forceStop) {      
        this.timeout = timeout;
        this.forceStop = forceStop;

        this.target = new Thread(target);       
        this.interrupter = new Thread(new Interrupter());

        barrier = new CyclicBarrier(2); // There will always be just 2 threads waiting on this barrier
    }

    public boolean execute() throws InterruptedException {  

        // Start target and interrupter
        target.start();
        interrupter.start();

        // Wait for target to finish or be interrupted by interrupter
        target.join();  

        interrupter.interrupt(); // stop the interrupter    
        try {
            barrier.await(); // Need to wait on this barrier to make sure status is set
        } catch (BrokenBarrierException e) {
            // Something horrible happened, assume we failed
            success = false;
        } 

        return success; // status is set in the Interrupter inner class
    }

    private class Interrupter implements Runnable {

        Interrupter() {}

        public void run() {
            try {
                Thread.sleep(timeout); // Wait for timeout period and then kill this target
                if (forceStop) {
                  target.stop(); // Need to use stop instead of interrupt since we're trying to kill this thread
                }
                else {
                    target.interrupt(); // Gracefully interrupt the waiting thread
                }
                System.out.println("done");             
                success = false;
            } catch (InterruptedException e) {
                success = true;
            }


            try {
                barrier.await(); // Need to wait on this barrier
            } catch (InterruptedException e) {
                // If the Child and Interrupter finish at the exact same millisecond we'll get here
                // In this weird case assume it failed
                success = false;                
            } 
            catch (BrokenBarrierException e) {
                // Something horrible happened, assume we failed
                success = false;
            }

        }

    }
}

Nazywa się to tak:

long timeout = 10000; // number of milliseconds before timeout
TimeOut t = new TimeOut(new PhotoProcessor(filePath, params), timeout, true);
try {                       
  boolean sucess = t.execute(); // Will return false if this times out
  if (!sucess) {
    // This thread timed out
  }
  else {
    // This thread ran completely and did not timeout
  }
} catch (InterruptedException e) {}  
Peter Goransson
źródło
3

Wysyłam ci fragment kodu, który pokazuje sposób rozwiązania problemu. Jako przykład czytam plik. Możesz użyć tej metody do innej operacji, ale musisz zaimplementować metodę kill (), aby główna operacja została przerwana.

mam nadzieję, że to pomoże


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Main class
 * 
 * @author el
 * 
 */
public class Main {
    /**
     * Thread which perform the task which should be timed out.
     * 
     * @author el
     * 
     */
    public static class MainThread extends Thread {
        /**
         * For example reading a file. File to read.
         */
        final private File fileToRead;
        /**
         * InputStream from the file.
         */
        final private InputStream myInputStream;
        /**
         * Thread for timeout.
         */
        final private TimeOutThread timeOutThread;

        /**
         * true if the thread has not ended.
         */
        boolean isRunning = true;

        /**
         * true if all tasks where done.
         */
        boolean everythingDone = false;

        /**
         * if every thing could not be done, an {@link Exception} may have
         * Happens.
         */
        Throwable endedWithException = null;

        /**
         * Constructor.
         * 
         * @param file
         * @throws FileNotFoundException
         */
        MainThread(File file) throws FileNotFoundException {
            setDaemon(false);
            fileToRead = file;
            // open the file stream.
            myInputStream = new FileInputStream(fileToRead);
            // Instantiate the timeout thread.
            timeOutThread = new TimeOutThread(10000, this);
        }

        /**
         * Used by the {@link TimeOutThread}.
         */
        public void kill() {
            if (isRunning) {
                isRunning = false;
                if (myInputStream != null) {
                    try {
                        // close the stream, it may be the problem.
                        myInputStream.close();
                    } catch (IOException e) {
                        // Not interesting
                        System.out.println(e.toString());
                    }
                }
                synchronized (this) {
                    notify();
                }
            }
        }

        /**
         * The task which should be timed out.
         */
        @Override
        public void run() {
            timeOutThread.start();
            int bytes = 0;
            try {
                // do something
                while (myInputStream.read() >= 0) {
                    // may block the thread.
                    myInputStream.read();
                    bytes++;
                    // simulate a slow stream.
                    synchronized (this) {
                        wait(10);
                    }
                }
                everythingDone = true;
            } catch (IOException e) {
                endedWithException = e;
            } catch (InterruptedException e) {
                endedWithException = e;
            } finally {
                timeOutThread.kill();
                System.out.println("-->read " + bytes + " bytes.");
                isRunning = false;
                synchronized (this) {
                    notifyAll();
                }
            }
        }
    }

    /**
     * Timeout Thread. Kill the main task if necessary.
     * 
     * @author el
     * 
     */
    public static class TimeOutThread extends Thread {
        final long timeout;
        final MainThread controlledObj;

        TimeOutThread(long timeout, MainThread controlledObj) {
            setDaemon(true);
            this.timeout = timeout;
            this.controlledObj = controlledObj;
        }

        boolean isRunning = true;

        /**
         * If we done need the {@link TimeOutThread} thread, we may kill it.
         */
        public void kill() {
            isRunning = false;
            synchronized (this) {
                notify();
            }
        }

        /**
         * 
         */
        @Override
        public void run() {
            long deltaT = 0l;
            try {
                long start = System.currentTimeMillis();
                while (isRunning && deltaT < timeout) {
                    synchronized (this) {
                        wait(Math.max(100, timeout - deltaT));
                    }
                    deltaT = System.currentTimeMillis() - start;
                }
            } catch (InterruptedException e) {
                // If the thread is interrupted,
                // you may not want to kill the main thread,
                // but probably yes.
            } finally {
                isRunning = false;
            }
            controlledObj.kill();
        }
    }

    /**
     * Start the main task and wait for the end.
     * 
     * @param args
     * @throws FileNotFoundException
     */
    public static void main(String[] args) throws FileNotFoundException {
        long start = System.currentTimeMillis();
        MainThread main = new MainThread(new File(args[0]));
        main.start();
        try {
            while (main.isRunning) {
                synchronized (main) {
                    main.wait(1000);
                }
            }
            long stop = System.currentTimeMillis();

            if (main.everythingDone)
                System.out.println("all done in " + (stop - start) + " ms.");
            else {
                System.out.println("could not do everything in "
                        + (stop - start) + " ms.");
                if (main.endedWithException != null)
                    main.endedWithException.printStackTrace();
            }
        } catch (InterruptedException e) {
            System.out.println("You've killed me!");
        }
    }
}

pozdrowienia

elou
źródło
3

Oto moja bardzo prosta w użyciu klasa pomocnicza do uruchomienia lub wywołania fragmentu kodu Java :-)

Opiera się to na doskonałą odpowiedź od BalusC

package com.mycompany.util.concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Calling {@link Callable#call()} or Running {@link Runnable#run()} code
 * with a timeout based on {@link Future#get(long, TimeUnit))}
 * @author pascaldalfarra
 *
 */
public class CallableHelper
{

    private CallableHelper()
    {
    }

    public static final void run(final Runnable runnable, int timeoutInSeconds)
    {
        run(runnable, null, timeoutInSeconds);
    }

    public static final void run(final Runnable runnable, Runnable timeoutCallback, int timeoutInSeconds)
    {
        call(new Callable<Void>()
        {
            @Override
            public Void call() throws Exception
            {
                runnable.run();
                return null;
            }
        }, timeoutCallback, timeoutInSeconds); 
    }

    public static final <T> T call(final Callable<T> callable, int timeoutInSeconds)
    {
        return call(callable, null, timeoutInSeconds); 
    }

    public static final <T> T call(final Callable<T> callable, Runnable timeoutCallback, int timeoutInSeconds)
    {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        try
        {
            Future<T> future = executor.submit(callable);
            T result = future.get(timeoutInSeconds, TimeUnit.SECONDS);
            System.out.println("CallableHelper - Finished!");
            return result;
        }
        catch (TimeoutException e)
        {
            System.out.println("CallableHelper - TimeoutException!");
            if(timeoutCallback != null)
            {
                timeoutCallback.run();
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        catch (ExecutionException e)
        {
            e.printStackTrace();
        }
        finally
        {
            executor.shutdownNow();
            executor = null;
        }

        return null;
    }

}
Pascal
źródło
2

Poniższy fragment kodu rozpocznie operację w osobnym wątku, a następnie zaczeka do 10 sekund na zakończenie operacji. Jeśli operacja nie zakończy się na czas, kod podejmie próbę anulowania operacji, a następnie kontynuuje na swój wesoły sposób. Nawet jeśli operacji nie można łatwo anulować, wątek nadrzędny nie będzie czekał na zakończenie wątku podrzędnego.

ExecutorService executorService = getExecutorService();
Future<SomeClass> future = executorService.submit(new Callable<SomeClass>() {
    public SomeClass call() {
        // Perform long-running task, return result. The code should check
        // interrupt status regularly, to facilitate cancellation.
    }
});
try {
    // Real life code should define the timeout as a constant or
    // retrieve it from configuration
    SomeClass result = future.get(10, TimeUnit.SECONDS);
    // Do something with the result
} catch (TimeoutException e) {
    future.cancel(true);
    // Perform other error handling, e.g. logging, throwing an exception
}

getExecutorService()Metoda może być realizowana na wiele sposobów. Jeśli nie masz żadnych szczególnych wymagań, możesz po prostu wezwać Executors.newCachedThreadPool()do pulowania wątków bez górnego limitu liczby wątków.

markusk
źródło
Jakie są wymagane przywozy? Jakie są SomeClassi Future?
ADTC
2

Jednej rzeczy, o której nie wspomniałem, jest to, że zabijanie wątków jest ogólnie złym pomysłem. Istnieją techniki tworzenia metod gwintowanych mogą zostać przerwane , ale różni się to od zabicia wątku po upływie określonego czasu.

Ryzyko związane z tym, co sugerujesz, polega na tym, że prawdopodobnie nie wiesz, w jakim stanie będzie wątek, gdy go zabijesz - więc ryzykujesz wprowadzeniem niestabilności. Lepszym rozwiązaniem jest upewnienie się, że kod wątkowy albo się nie zawiesi, albo ładnie odpowie na żądanie przerwania.

Dan Puzey
źródło
Bez kontekstu zdanie takie jak twoje brzmi zbyt restrykcyjnie. W środowisku akademickim bardzo często potrzebuję przetestować coś do przekroczenia limitu czasu, a kiedy to nastąpi, po prostu porzucam wszystkie obliczenia i zapisuję, że limit czasu wystąpił. Prawdopodobnie jest to rzadkie w branży, ale nadal ...
Alessandro S.,
@AlessandroS: to rozsądny punkt, chociaż OP poprosił o „lepsze rozwiązania”, przez co rozumiałem, że wytrzymałość i niezawodność były lepsze niż brutalna siła.
Dan Puzey,
2

Świetna odpowiedź BalusC:

ale aby dodać, że sam limit czasu nie przerywa samego wątku. nawet jeśli sprawdzasz za pomocą while (! Thread.interrupt ()) w swoim zadaniu. jeśli chcesz upewnić się, że wątek został zatrzymany, powinieneś także upewnić się, że future.cancel () jest wywoływany, gdy wyjątek limitu czasu jest wyłapany.

package com.stackoverflow.q2275443; 

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


public class Test { 
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Task());

        try { 
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            //Without the below cancel the thread will continue to live 
            // even though the timeout exception thrown.
            future.cancel();
            System.out.println("Terminated!");
        } 

        executor.shutdownNow();
    } 
} 

class Task implements Callable<String> {
    @Override 
    public String call() throws Exception {
      while(!Thread.currentThread.isInterrupted()){
          System.out.println("Im still running baby!!");
      }          
    } 
} 
Robocide
źródło
0

Myślę, że odpowiedź zależy głównie od samego zadania.

  • Czy robi to w kółko jedno zadanie?
  • Czy konieczne jest, aby limit czasu przerywał aktualnie uruchomione zadanie natychmiast po jego upływie?

Jeśli pierwsza odpowiedź brzmi „tak”, a druga „nie”, możesz uprościć to tak:

public class Main {

    private static final class TimeoutTask extends Thread {
        private final long _timeoutMs;
        private Runnable _runnable;

        private TimeoutTask(long timeoutMs, Runnable runnable) {
            _timeoutMs = timeoutMs;
            _runnable = runnable;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() < (start + _timeoutMs)) {
                _runnable.run();
            }
            System.out.println("execution took " + (System.currentTimeMillis() - start) +" ms");
        }

    }

    public static void main(String[] args) throws Exception {
        new TimeoutTask(2000L, new Runnable() {

            @Override
            public void run() {
                System.out.println("doing something ...");
                try {
                    // pretend it's taking somewhat longer than it really does
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

Jeśli nie jest to opcja, zawęź wymagania - lub pokaż kod.

sfussenegger
źródło
0

Szukałem usługi Executor, która może przerwać wszystkie przekroczone limity czasu Runnables przez nią wykonane, ale nie znalazłem żadnej. Po kilku godzinach stworzyłem taki jak poniżej. Tę klasę można zmodyfikować w celu zwiększenia odporności.

public class TimedExecutorService extends ThreadPoolExecutor {
    long timeout;
    public TimedExecutorService(int numThreads, long timeout, TimeUnit unit) {
        super(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(numThreads + 1));
        this.timeout = unit.toMillis(timeout);
    }

    @Override
    protected void beforeExecute(Thread thread, Runnable runnable) {
        Thread interruptionThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // Wait until timeout and interrupt this thread
                    Thread.sleep(timeout);
                    System.out.println("The runnable times out.");
                    thread.interrupt();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        interruptionThread.start();
    }
}

Stosowanie:

public static void main(String[] args) {

    Runnable abcdRunnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("abcdRunnable started");
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                // logger.info("The runnable times out.");
            }
            System.out.println("abcdRunnable ended");
        }
    };

    Runnable xyzwRunnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("xyzwRunnable started");
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                // logger.info("The runnable times out.");
            }
            System.out.println("xyzwRunnable ended");
        }
    };

    int numThreads = 2, timeout = 5;
    ExecutorService timedExecutor = new TimedExecutorService(numThreads, timeout, TimeUnit.SECONDS);
    timedExecutor.execute(abcdRunnable);
    timedExecutor.execute(xyzwRunnable);
    timedExecutor.shutdown();
}
Tomek
źródło
0

Teraz spotykam taki problem. Zdarza się zdekodować obraz. Proces dekodowania zajmuje zbyt dużo czasu, aby ekran pozostawał czarny. Dodam kontroler czasu: kiedy czas jest zbyt długi, wyskakuj z bieżącego wątku. Oto diff:

   ExecutorService executor = Executors.newSingleThreadExecutor();
   Future<Bitmap> future = executor.submit(new Callable<Bitmap>() {
       @Override
       public Bitmap call() throws Exception {
       Bitmap bitmap = decodeAndScaleBitmapFromStream(context, inputUri);// do some time consuming operation
       return null;
            }
       });
       try {
           Bitmap result = future.get(1, TimeUnit.SECONDS);
       } catch (TimeoutException e){
           future.cancel(true);
       }
       executor.shutdown();
       return (bitmap!= null);
Liu Jing
źródło
0

Miałem ten sam problem. Więc wpadłem na takie proste rozwiązanie.

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

Gwarantuje, że jeśli blok nie zostanie wykonany w wyznaczonym terminie. proces zostanie zakończony i zgłosi wyjątek.

przykład:

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
źródło
0

W rozwiązaniu podanym przez BalusC główny wątek pozostanie zablokowany na czas oczekiwania. Jeśli masz pulę wątków z więcej niż jednym wątkiem, potrzebujesz tej samej liczby dodatkowych wątków, które będą korzystać z Future.get (długi limit czasu, jednostka TimeUnit) blokującego wywołanie, aby czekać i zamykać wątek, jeśli przekroczy on limit czasu.

Ogólnym rozwiązaniem tego problemu jest utworzenie programu ThreadPoolExecutor Decorator, który może dodać funkcję limitu czasu. Ta klasa Dekoratora powinna tworzyć tyle wątków, ile ma ThreadPoolExecutor, a wszystkie te wątki powinny być używane tylko do oczekiwania i zamknięcia ThreadPoolExecutor.

Klasę ogólną należy zaimplementować jak poniżej:

import java.util.List;
import java.util.concurrent.*;

public class TimeoutThreadPoolDecorator extends ThreadPoolExecutor {


    private final ThreadPoolExecutor commandThreadpool;
    private final long timeout;
    private final TimeUnit unit;

    public TimeoutThreadPoolDecorator(ThreadPoolExecutor threadpool,
                                      long timeout,
                                      TimeUnit unit ){
        super(  threadpool.getCorePoolSize(),
                threadpool.getMaximumPoolSize(),
                threadpool.getKeepAliveTime(TimeUnit.MILLISECONDS),
                TimeUnit.MILLISECONDS,
                threadpool.getQueue());

        this.commandThreadpool = threadpool;
        this.timeout=timeout;
        this.unit=unit;
    }

    @Override
    public void execute(Runnable command) {
        super.execute(() -> {
            Future<?> future = commandThreadpool.submit(command);
            try {
                future.get(timeout, unit);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (ExecutionException | TimeoutException e) {
                throw new RejectedExecutionException(e);
            } finally {
                future.cancel(true);
            }
        });
    }

    @Override
    public void setCorePoolSize(int corePoolSize) {
        super.setCorePoolSize(corePoolSize);
        commandThreadpool.setCorePoolSize(corePoolSize);
    }

    @Override
    public void setThreadFactory(ThreadFactory threadFactory) {
        super.setThreadFactory(threadFactory);
        commandThreadpool.setThreadFactory(threadFactory);
    }

    @Override
    public void setMaximumPoolSize(int maximumPoolSize) {
        super.setMaximumPoolSize(maximumPoolSize);
        commandThreadpool.setMaximumPoolSize(maximumPoolSize);
    }

    @Override
    public void setKeepAliveTime(long time, TimeUnit unit) {
        super.setKeepAliveTime(time, unit);
        commandThreadpool.setKeepAliveTime(time, unit);
    }

    @Override
    public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
        super.setRejectedExecutionHandler(handler);
        commandThreadpool.setRejectedExecutionHandler(handler);
    }

    @Override
    public List<Runnable> shutdownNow() {
        List<Runnable> taskList = super.shutdownNow();
        taskList.addAll(commandThreadpool.shutdownNow());
        return taskList;
    }

    @Override
    public void shutdown() {
        super.shutdown();
        commandThreadpool.shutdown();
    }
}

Powyższy dekorator może być używany jak poniżej:

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args){

        long timeout = 2000;

        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 10, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>(true));

        threadPool = new TimeoutThreadPoolDecorator( threadPool ,
                timeout,
                TimeUnit.MILLISECONDS);


        threadPool.execute(command(1000));
        threadPool.execute(command(1500));
        threadPool.execute(command(2100));
        threadPool.execute(command(2001));

        while(threadPool.getActiveCount()>0);
        threadPool.shutdown();


    }

    private static Runnable command(int i) {

        return () -> {
            System.out.println("Running Thread:"+Thread.currentThread().getName());
            System.out.println("Starting command with sleep:"+i);
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                System.out.println("Thread "+Thread.currentThread().getName()+" with sleep of "+i+" is Interrupted!!!");
                return;
            }
            System.out.println("Completing Thread "+Thread.currentThread().getName()+" after sleep of "+i);
        };

    }
}
Sumeet Sahu
źródło