Como exceder o tempo limite de um thread

255

Eu quero executar um thread por um período fixo de tempo. Se não for concluído nesse período, quero matá-lo, lançar alguma exceção ou lidar com isso de alguma forma. Como pode ser feito?

Uma maneira de fazer isso, como descobri neste thread, é usar um TimerTask dentro do método run () do Thread.

Existem soluções melhores para isso?

 
Edição: Adicionando uma recompensa como eu precisava de uma resposta mais clara. O código ExecutorService fornecido abaixo não soluciona meu problema. Por que devo dormir () após a execução (algum código - não tenho controle sobre esse trecho de código)? Se o código for concluído e o sleep () for interrompido, como pode ser um tempo limite?

A tarefa que precisa ser executada não está no meu controle. Pode ser qualquer parte do código. O problema é que esse pedaço de código pode ter um loop infinito. Eu não quero que isso aconteça. Então, eu só quero executar essa tarefa em um thread separado. O encadeamento pai precisa aguardar até que o encadeamento termine e precise saber o status da tarefa (ou seja, se atingiu o tempo limite ou se ocorreu alguma exceção ou se é um sucesso). Se a tarefa entrar em um loop infinito, meu encadeamento pai continuará aguardando indefinidamente, o que não é uma situação ideal.

java_geek
fonte
Edição: Adicionando uma recompensa como eu precisava de uma resposta mais clara. o código ExecutorService fornecido abaixo não soluciona meu problema. Por que devo dormir () depois de executar meu código? Se o código for concluído e o sleep () for interrompido, como pode ser um tempo limite?
Java_geek
7
Isso sleep()foi apenas um esboço para representar "uma tarefa em execução há muito tempo". Apenas substituí-lo com a sua tarefa real;)
BalusC
1
... uma "tarefa de execução longa" que responde às interrupt()chamadas em seu encadeamento ... nem todas as chamadas "bloqueadas" o fazem, como tentei apontar na minha resposta. As especificidades da tarefa que você está tentando abortar fazem uma enorme diferença na abordagem que deve ser usada. Mais informações sobre a tarefa seriam úteis.
21410
Se essas respostas não resolverem o problema, acho que mais detalhes / código devem ajudar a responder.
Elister 24/02
Esses segmentos que você deseja limitar por tempo; eles estão fazendo chamadas de bloqueio ou estão em algum loop em que você pode facilmente verificar alguma variável para ver se é hora de sair?
Scott Smith

Respostas:

376

De fato, em ExecutorServicevez de usar em vez de Timer, aqui está um 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!";
    }
}

Jogue um pouco com o timeoutargumento do Future#get()método, por exemplo, aumente para 5 e você verá que o thread termina. Você pode interceptar o tempo limite no catch (TimeoutException e)bloco.

Atualização: para esclarecer um mal-entendido conceitual, nãosleep() é necessário. É usado apenas para fins de demonstração / SSCCE. Basta fazer sua tarefa de longa duração ali no lugar de . Dentro da sua tarefa de longa execução, você deve verificar se o encadeamento não é interrompido da seguinte maneira:sleep()

while (!Thread.interrupted()) {
    // Do your long running task here.
}
BalusC
fonte
24
Substitua Thread.sleep(4000)por outra declaração de longa duração e o exemplo não funcionará. Em outras palavras, este exemplo funcionaria apenas se o Taskfosse projetado para entender a Thread.isInterrupted()mudança de status.
yegor256
@BalusC Tentei essa abordagem tentando encerrar meus threads, mas não consegui fazê-lo funcionar. Você pode vê-lo aqui: stackoverflow.com/questions/35553420/…
syfantid
Como o InterruptedException é processado pelo future.cancel (true)?
bolei
1
n número de pessoas comentou sobre o nome do pacote, e aqui está mais um +1 para ele. É uma habilidade tão boa de ser absorvida. Obrigado!
Ashwin Tumma
@BalusC Tenho uma dúvida, se o Futuro será executado de forma síncrona e se demorar mais de um tempo predefinido, será encerrado. Caso contrário, ele será executado no futuro algum tempo enquanto isso estamos contando com o tempo ... Obrigado
Adeel Ahmad
49

Não existe uma maneira 100% confiável de fazer isso para qualquer tarefa antiga. A tarefa deve ser escrita com essa capacidade em mente.

Bibliotecas Java principais, como ExecutorServicecancelam tarefas assíncronas com interrupt()chamadas no thread de trabalho. Portanto, por exemplo, se a tarefa contiver algum tipo de loop, você deverá verificar seu status de interrupção em cada iteração. Se a tarefa estiver executando operações de E / S, elas também devem ser interrompidas - e a configuração pode ser complicada. De qualquer forma, lembre-se de que o código deve verificar ativamente se há interrupções; definir uma interrupção não necessariamente faz nada.

Obviamente, se sua tarefa for um loop simples, basta verificar o tempo atual em cada iteração e desistir quando um tempo limite especificado tiver decorrido. Um segmento de trabalho não é necessário nesse caso.

erickson
fonte
Na minha experiência, o único código que não está reagindo para começar a ser interrompido é o bloqueio no código nativo (aguardando o sistema operacional).
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Concordo, mas isso é muito código. O que quero dizer é que não há mecanismo de propósito geral para isso; você precisa entender a política de interrupção da tarefa.
30513 Erickson
@ erickson, eu concordo com o seu. Diretamente, deve haver uma política de cancelamento definida para cada tarefa, se você estiver interessado em interrompê-la. Ou o encadeamento deve estar ciente do que deve fazer quando for interrompido. Afinal, interromper e interromper qualquer encadeamento é apenas uma solicitação que o encadeamento de destino pode aceitar ou rejeitar; portanto, é melhor escrever uma tarefa tendo isso em mente.
AKS
o executorservice não pode optar por executar a tarefa no segmento de chamada? O executorservice também pode optar por executar a tarefa em algum momento no futuro?
precisa saber é o seguinte
@ user1232726 O execute()método da interface principal Executorpode executar uma tarefa no thread de chamada. Não há declaração semelhante para os submit()métodos dessas instâncias de ExecutorServiceretorno Future. A implicação do serviço é que existem threads de trabalho que precisam ser limpos por meio do encerramento e que as tarefas são executadas de forma assíncrona. Dito isto, não há nada no contrato que diga que ExecutorServiceé proibido executar tarefas no encadeamento de envio; essas garantias vêm das APIs de implementação, como Executorsfábricas.
21915
13

Considere usar uma instância do ExecutorService . Ambos invokeAll()e invokeAny()métodos estão disponíveis com um timeoutparâmetro.

O encadeamento atual será bloqueado até que o método seja concluído (não tenho certeza se isso é desejável), porque as tarefas foram concluídas normalmente ou o tempo limite foi atingido. Você pode inspecionar o Future(s) retornado (s) para determinar o que aconteceu.

Drew Wills
fonte
9

Supondo que o código do thread esteja fora de seu controle:

A partir da documentação Java mencionada acima:

E se um thread não responder ao Thread.interrupt?

Em alguns casos, você pode usar truques específicos do aplicativo. Por exemplo, se um encadeamento estiver aguardando em um soquete conhecido, você poderá fechá-lo para fazer com que ele retorne imediatamente. Infelizmente, não existe realmente nenhuma técnica que funcione em geral. Deve-se observar que em todas as situações em que um thread em espera não responde ao Thread.interrupt, ele também não responde ao Thread.stop. Esses casos incluem ataques deliberados de negação de serviço e operações de E / S para as quais thread.stop e thread.interrupt não funcionam corretamente.

Bottom Line:

Certifique-se de que todos os threads possam ser interrompidos, ou então você precisa de conhecimentos específicos sobre o thread - como ter um sinalizador para definir. Talvez você possa exigir que a tarefa seja dada junto com o código necessário para interrompê-la - defina uma interface com um stop()método. Você também pode avisar quando não conseguiu parar uma tarefa.

Peter Tseng
fonte
8

BalusC disse:

Atualização: para esclarecer um mal-entendido conceitual, o sono () não é necessário. É usado apenas para fins de SSCCE / demonstração. Basta realizar sua tarefa de longa duração, bem no lugar do sono ().

Mas se você substituir Thread.sleep(4000);por for (int i = 0; i < 5E8; i++) {}, ele não será compilado, porque o loop vazio não gera um InterruptedException.

E para que o thread seja interrompível, ele precisa lançar um InterruptedException.

Isso parece ser um problema sério para mim. Não vejo como adaptar essa resposta para trabalhar com uma tarefa geral de longa duração.

Editado para adicionar: Eu avaliei isso como uma nova pergunta: [ interrompendo um encadeamento após um tempo fixo, ele precisa lançar InterruptedException? ]

user1310503
fonte
A forma como eu fazer isso é adicionar um'throws Exception' na Classe pública <T> chamado {} método
Roberto Linares
5

Eu acho que você deve dar uma olhada nos mecanismos adequados de manipulação de simultaneidade (threads executando em loops infinitos não soam bem por si só). Certifique-se de ler um pouco sobre o tópico Threads "matar" ou "parar" .

O que você está descrevendo parece muito com um "encontro", então você pode dar uma olhada no CyclicBarrier .

Pode haver outras construções (como usar CountDownLatch, por exemplo) que podem resolver seu problema (um encadeamento aguardando um tempo limite para a trava, o outro deve fazer a contagem regressiva da trava se tiver feito seu trabalho, o que liberaria seu primeiro encadeamento após um tempo limite ou quando a contagem regressiva da trava for chamada).

Normalmente, recomendo dois livros nesta área: Programação Simultânea em Java e Java Simultaneidade na Prática .

Dieter
fonte
5

Eu criei uma classe auxiliar apenas para isso há algum tempo. Funciona bem:

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

        }

    }
}

É chamado assim:

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
fonte
3

Publico um código que mostra uma maneira de resolver o problema. Como exemplo, estou lendo um arquivo. Você pode usar esse método para outra operação, mas precisa implementar o método kill () para que a operação principal seja interrompida.

espero que ajude


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!");
        }
    }
}

Saudações

elou
fonte
3

Aqui está minha classe auxiliar realmente simples de usar para executar ou chamar parte do código Java :-)

Isso se baseia na excelente resposta da 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
fonte
2

O fragmento a seguir iniciará uma operação em um encadeamento separado e aguarde até 10 segundos para que a operação seja concluída. Se a operação não for concluída a tempo, o código tentará cancelar a operação e continue em seu caminho alegre. Mesmo que a operação não possa ser cancelada facilmente, o encadeamento pai não esperará o término do encadeamento filho.

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
}

O getExecutorService()método pode ser implementado de várias maneiras. Se você não possui nenhum requisito específico, pode simplesmente solicitar o Executors.newCachedThreadPool()pool de threads sem limite superior no número de threads.

markusk
fonte
Quais são as importações necessárias? O que são SomeClasse Future?
ADTC
2

Uma coisa que eu não vi mencionado é que matar tópicos geralmente é uma má ideia. Existem técnicas para tornar os métodos de encadeamento abortáveis ​​de maneira limpa , mas isso é diferente de simplesmente matar um encadeamento após um tempo limite.

O risco com o que você está sugerindo é que você provavelmente não sabe em que estado o thread estará quando o matar - então corre o risco de introduzir instabilidade. Uma solução melhor é garantir que seu código encadeado não se desligue ou responda bem a uma solicitação de cancelamento.

Dan Puzey
fonte
Sem um contexto, uma declaração como a sua parece muito restritiva. No cenário acadêmico, tenho muitas vezes a necessidade de testar algo até um tempo limite e, quando isso ocorre, simplesmente abro toda a computação e registro que o tempo limite ocorreu. Provavelmente é raro na indústria, mas ainda assim ...
Alessandro S.
@AlessandroS: esse é um ponto razoável, embora o OP tenha solicitado "melhores soluções", pelo que entendi que a robustez e a confiabilidade eram preferidas à força bruta.
Dan Puzey
2

Ótima resposta de BalusC:

mas Apenas para adicionar que o tempo limite em si não interrompe o próprio thread. mesmo se você estiver verificando com while (! Thread.interrupted ()) em sua tarefa. se você quiser garantir que o encadeamento seja interrompido, verifique se future.cancel () será chamado quando a exceção de tempo limite for capturada.

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

Eu acho que a resposta depende principalmente da tarefa em si.

  • Está fazendo uma tarefa repetidamente?
  • É necessário que o tempo limite interrompa uma tarefa em execução no momento imediatamente após expirar?

Se a primeira resposta for sim e a segunda for não, você pode mantê-la simples assim:

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

Se isso não for uma opção, reduza seus requisitos - ou mostre algum código.

sfussenegger
fonte
0

Eu estava procurando por um ExecutorService que possa interromper todos os Runnables expirados, mas não encontrou nenhum. Depois de algumas horas, criei um como abaixo. Esta classe pode ser modificada para aumentar a robustez.

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

Uso:

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

Agora, encontro uma questão como esta. Isso acontece para decodificar imagem. O processo de decodificação leva muito tempo para que a tela fique preta. l adiciono um controle de tempo: quando o tempo for muito longo, então apareça no Thread atual. O seguinte é o 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
fonte
0

Eu tive o mesmo problema. Então, eu vim com uma solução simples como esta.

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

Garante que, se o bloco não for executado dentro do prazo. o processo será encerrado e lançará uma exceção.

exemplo:

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

Na solução fornecida pelo BalusC , o thread principal permanecerá bloqueado pelo período de tempo limite. Se você tiver um conjunto de encadeamentos com mais de um encadeamento, precisará do mesmo número de encadeamentos adicionais que usarão a chamada de bloqueio Future.get (timeout longo, unidade TimeUnit) para aguardar e fechar o encadeamento se exceder o período de tempo limite.

Uma solução genérica para esse problema é criar um Decorador ThreadPoolExecutor que possa adicionar a funcionalidade de tempo limite. Essa classe Decorator deve criar quantos threads o ThreadPoolExecutor possui e todos esses threads devem ser usados ​​apenas para aguardar e fechar o ThreadPoolExecutor.

A classe genérica deve ser implementada como abaixo:

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

O decorador acima pode ser usado como abaixo:

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
fonte