Páginas

quarta-feira, 21 de setembro de 2011

Implementando Timeout em Java

Normalmente quando acessamos recursos que não temos controle, estes recursos podem demorar a responder e nosso código pode ficar refém desta requisição que nunca termina. Nestas situações entram em ação os controles de timeout.
Recursos como requisições http já trazem configurações de timeout definidas no código, mas as vezes este recurso não está disponível na API em uso, nesta postagem vamos descrever uma das possibilidades de implementação de timeout via código.
A abordagem que vamos utilizar é a mesma abordagem utilizada na postagem sobre Web Service com Cliente Assíncrono.

O método externo

// Esta classe é apenas um exemplo de código não controlado, seria a nossa API externa.
static class ExecucaoNaoControlada {

  // O momento de retorno deste método não é conhecido, pode ocorrer em qualquer momento.
  public String executar() throws Exception {
    System.out.println("# Informe seu nome:");
    return new BufferedReader(new InputStreamReader(System.in)).readLine();
  }
}
Este trecho de código é apenas ilustrativo, este seria o método de uma API que estamos utilizando que não implementa timeout nativamente, mas este método pode demorar para concluir e nosso código não pode esperar um tempo indeterminado.

Uma classe de conexão

// Esta classe apenas faz a conexão entre o nosso código e o código não controlado
static class ClasseDeConexao implements Callable<String> {

  @Override
  public String call() throws Exception {
    return new ExecucaoNaoControlada().executar();
  }
}
Estre trecho de código é uma classe de conexão. Esta classe fará a junção entre a API sendo utilizada e o nosso código. Esta classe adicional irá nos trazer a possibilidade de controle de tempo através da interface Callable.

Controlando a execução

Vamos conhecer os agentes responsáveis pelo proposto nesta postagem.
java.util.concurrent.Executors - Esta classe é responsável pela criação de um gestor de pool de threads, no nosso exemplo um gestor de thread única, outras opções podem ser avaliadas dependendo dos serviços que serão conectados.
java.util.concurrent.ExecutorService - Esta classe será o nosso gestor do pool de threads.
java.util.concurrent.Future - Esta classe será responsável por manter o controle sobre a nossa classe de conexão.

Tudo junto

package testes;

import java.io.BufferedReader;
import java.io.InputStreamReader;
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;

public class Timeout {
 
  static ExecutorService tpes = Executors.newSingleThreadExecutor();

  public static void main(String[] args) {
    Future<String> handle = tpes.submit(new ClasseDeConexao());
  
    try {
      System.out.println("Iniciando chamada com timeout.");
      String resposta = handle.get(10, TimeUnit.SECONDS);
      System.out.println("O método retornou: " + resposta);
    } catch(InterruptedException e) {
      System.err.println("A thread principal foi interrompida.");
    } catch(ExecutionException e) {
      System.err.println("A execução do método lançou uma exceção.");
    } catch(TimeoutException e) {
       System.err.println("O método estourou o tempo limite (timeout).");
    }
  
    System.out.println("Encerrando aplicativo.");
    System.exit(0);
  }
 
  // Esta classe apenas faz a conexão entre o nosso código e o código não controlado
  static class ClasseDeConexao implements Callable<String> {

     @Override
    public String call() throws Exception {
      return new ExecucaoNaoControlada().executar();
    }
  }

  // Esta classe é apenas um exemplo de código não controlado, seria a nossa API externa.
  static class ExecucaoNaoControlada {

    // O momento de retorno deste método não é conhecido, pode ocorrer em qualquer momento.
    public String executar() throws Exception {
      System.out.println("# Informe seu nome:");
      return new BufferedReader(new InputStreamReader(System.in)).readLine();
    }
  
  }
}
Através do nosso gestor de threads nós solicitamos a submissão de uma classe que implementa Callable para execução através do pool de threads, neste momento a classe ainda não será enviada para execução, mas nos é retornado um handle do tipo Future.
Através do método get do nosso handle a classe é enviada para execução no pool de threads e será iniciada assim que possível. O método get é um método bloqueante e a execução não continuará até que a classe chamada retorne um resultado, mas os dois parâmetros do método definem o tempo que nosso código poderá esperar para que um resultado seja entregue, caso este tempo seja atingido uma exceção do tipo TimeoutException será lançada, mas caso o resultado seja entregue em tempo, o código irá continuar sua execução no caminho normal.
Faça testes com o código de exemplo, informando um texto assim que solicitado, e também não informando nada e deixando o tempo esgotar.

6 comentários:

  1. Me ajudou muito teu código, Obrigado!

    ResponderExcluir
  2. Valeu, Claudio. Dizem que, não importa quanto tempo passe, a internet não perdoa. Mas, por outro lado, ajuda, mesmo muito tempo depois. Isto era exatamente o que eu procurava: como implementar uma JOptionPane.showInputDialog (ou JOptionPane.showConfirmDialog) com resposta default por timeout. Seu texto com exemplo simples, claro e muito bem explicado, está de parabéns.

    ResponderExcluir

Olá! Antes de postar seu comentário, por favor, observe que comentários técnicos, elogios e sugestões são antecipadamente agradecidos, esclarecimentos sobre os conceitos envolvidos na postagem serão respondidos da melhor forma possível, mas pedidos de ajuda técnica ou suporte individual deverão ser feitos através do formulário de contato. Grato!