Executar tarefas em threads e interromper por timeout

6 respostas
D

Caros,

Tenho um sistema que precisa executar múltiplas tarefas em parelelo e para isso usei o conceito de pool de threads.

A principio desenvolvi uma solução para atender meus requisitos. Porém estou apanhando para fazer com que cada tarefa execute dentro de um período de timeout ou então seja interrompida.

Segue uma a implementação (obviamente simplificada) para uma possível ajuda.

Este interrupt() não tem efeito sobre a thread em execução, pois a thread a ser interrompida não faz uso de nada da APi de Threads que lance um InterruptedException (ex: Thread.sleep()).

Sei que poderia usar o Thread Pool do Java, com Executor, mas ainda não vi uma forma clara de fazer esse timeout da execução da tarefa.

Sugestões?

package test.thread.pool;

import java.util.ArrayList;
import java.util.List;

public class ThreadPoolTest {
	
	public static void main(String[] args) {
		ThreadPoolTest test = new ThreadPoolTest();
		test.go();
	}
	
	public void go() {
		ThreadPool pool = new ThreadPool(10);
		pool.start();
	}
	
	private class ThreadPool {
		
		private int maxThreads;
		private List<MyTask> threads = new ArrayList<MyTask>();
		
		public ThreadPool(int maxThreads) {
			this.maxThreads = maxThreads;
		}
		
		public void start() {
			for( int i=0; i<maxThreads; i++ ) {
				MyTask t = new MyTask(threads);
				threads.add(t);
				t.start();
			}
			
			TimeoutEnforcement timeout = new TimeoutEnforcement(threads);
			timeout.start();
		}
	}
	
	private class MyTask extends Thread {
		private long start = -1;
		private boolean running = false;
		private List><MyTask> threads;
		
		public MyTask(List<MyTask> threads) {
			this.threads = threads;
		}
		
		public void run() {
			running = true;
			start = System.currentTimeMillis();
			
			int rand = (int) (Math.random() * 10) * 10000;
			System.out.println( this.getId()+": "+rand );
			try {
				process(rand);
				System.out.println( this.getId()+": FINISHED" );
				
			} catch(Exception e) {
				System.err.println(this.getId()+": error" );
				e.printStackTrace();
			} finally {
				this.threads.remove(this);
				running = false;
			}
		}
		
		public void process(int value) throws InterruptedException {
			String a = "";
			for(int i=0; i<value; i++) {
				a += " ";
				// INTERRUPT SÓ FUNCIONA COM ESTA LINHA
				//Thread.sleep(1);
			}
		}

		public long getStart() {
			return start;
		}

		public boolean isRunning() {
			return running;
		}
	}
	
	private class TimeoutEnforcement extends Thread {
		
		private List><MyTask> threads;
		
		public TimeoutEnforcement(List<MyTask> threads) {
			this.threads = threads;
		}
		
		public void run() {
			System.out.println("*** TimeoutEnforcement STARTED ***");
			while( threads.size() > 0 ) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
				}
				System.out.println("*** TimeoutEnforcement RUNNING ***");
				
				for( MyTask t: threads ) {
					
					if( !t.isRunning() || t.getStart()<0 ) {
						continue;
					}
					
					long now = System.currentTimeMillis();
					long elapsed = now - t.getStart();
					
					if( elapsed > 2000 ) {
						t.interrupt();
						System.out.println("INTERRUPTED: "+t.getId());
					}
				} //for()
			} //while()

			System.out.println("*** TimeoutEnforcement FINISHED ***");
		}
	}

}

6 Respostas

D

Coloquei “stop” no lugar de “interrupt” na linha 112 e funcionou neste teste ai. Porém, no sistema real não rolou não. Ele não para a thread.

V

Não tem como simplesmente interromper a execução de uma Thread. Você deveria ter lido o javadoc ameaçador do método stop(), lá você veria que ele não deve ser usado em hipótese alguma.

Você só consegue garantir um timeout se o comando que sua thread executa puder ser executado aos poucos. Nesse caso, use o System.currentTimeMillis() para obter quanto tempo transcorreu desde o início do processamento e saia do loop da thread se o timeout estourar. Seria como deixar seu método process assim:

public void process(int value) {  
            long before = System.currentTimeMillis();
            String a = "";  
            for(int i=0; i<value; i++) {  
                a += " ";  
                long elapsed = System.currentTimeMillis() - before;
                if (elapsed > timeout || Thread.isInterrupted())
                   break;
            }  
        }

Se você quiser uma threads, use o interrupt(). Mas seu método terá que ser programado para isso, já que o interrupt() apenas sinaliza o desejo de uma thread externa de interromper a thread que receber o interrupt().
Além do sleep e wait, vc pode testar o valor Thread.isInterrupted() para saber se o interrupt foi chamado.

D

Pois é Viny. Me alertei ao fato de “stop” e “destroy” serem deprecated, porém era a última tentativa de tentar abortar threads do meu processo.

O fato é que estas threads executam tarefas que eu não tenho controle sobre a execução, ou seja, não posso executar aos poucos e abortar sua execução.

Porém, acreditei que eu poderia simplesmente abortar a execução geral de uma thread “por fora”, mas vejo que não dá mesmo.

Conhece alguma solução para o caso?

Acabei também achando um “bug” na aplicação e coloquei um timeout em um serviço HTTP que tava dando problema. Acho que resolvi pontualmente o problema aqui, porém, nada impede que outros “timeouts” possam ocorrer e meu sistema entra em “hang”.

V

Se os métodos não dão suporte, não tem solução. =/

S

Não teria como você forçar uma exceção nesta tarefa? Por exemplo, se estiver lendo algum socket ou arquivo, tenta dar um close no stream ou mudar algum atributo do objeto. Desta forma estaria forçando um erro na tarefa e saindo do hang.

D

Até poderia criar um método “cancel” na minha thread e ela chamar o “close” ou algo do tipo do Stream. É uma ideia sim. Posso testar.

Criado 2 de junho de 2011
Ultima resposta 3 de jun. de 2011
Respostas 6
Participantes 3