Um caso de uso
Imagine que temos uma aplicação que mostra o código de uma página HTML dentro de um campo de texto. Para pegar o código de uma página, temos que abrir uma conexão com uma URL, ler o InputStream e em seguida atualizar o campo de texto com o resultado. Vamos as nossas soluções.Caso 1: Abrir URL ao clique do botão
Nesse primeiro exemplo, iremos abrir a URL quando o usuário clicar no botão, veja o código:EventHandlercenario1 = e -> { try { txtResultado.setText(carregaPagina(txtUrl.getText())); } catch (Exception e1) { e1.printStackTrace(); } };
Bem, esse código funciona, mas como ele trava a execução, a aplicação fica travada quando isso acontece:
Na imagem não fica nítido, mas a aplicação fica com o botão como se estivesse apertado. Como resolver isso?
Caso 2: Abrir URL em uma thread separada
Essa solução é a mais óbvia para quem já tem um pouco de intimidade com Java. O que podemos fazer é simplesmente abrir a URL em uma thread separada e pronto! A operação de abrir a URL seria em paralelo e atualização do campo de texto só feita quando terminassemos de ler a URL, veja o código:
EventHandlercenario2 = e -> { Thread t = new Thread(() -> { try { txtResultado.setText(carregaPagina(txtUrl.getText())); } catch (Exception e1) { e1.printStackTrace(); } }); t.start(); };
Legal, mas ao clicar algumas vezes no botão teremos o seguinte erro:
java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
E agora?
Threads e JavaFX
O ponto é que como JavaFX é quem coordena a modificação da parte visual da aplicação, ele não deixa que coisas feitas em outra thread tente atuar diretamente na view, assim ele tem controle da atualização da tela. Claro que os desenvolvedores da API já sabiam que fazer coisas em paralelo eram normais, assim eles arrumaram uma forma de ajudar você a criar aplicações que tenham execução de tarefas pesadas e não travar a thread do JavaFX!Platform.runLater
Com esse método estático da classe Platform, podemos infomar ao JavaFX uma thread que será executada sob o controle dele, assim não teríamos o erro que tivemos no caso 2. Veja o nosso caso do clique:Massa, agora não temos erro! No entanto, notem que essa thread que criamos está sob o controle do JavaFX, mas mesmo assim ela trava o botão... Como resolver isso de vez?
Task
As tasks são a solução! Com elas podemos executar algo em paralelo e pegar o resultado depois para sim atualizar a nossa aplicação. O funcionamento é simples, vamos focar em três principais métodos:
- call: É onde fazemos nossa tarefa pesada. Esse médoto o JavaFX não está cuidando, logo não devemos alterar nada da view aqui;
- succeded: Quando há o sucesso na execução do método call, esse método é chamado e dele podemos pegar o resultado do call usando o método getValue;
- failed: Esse método já é chamado quando temos uma exceção na execução do método call. Nele também podemos pegar a exceção lançada com o método getExeception.
Pronto! Isso é o suficiente para começar a usar a Task, mas notem que um ponto importante é que a Task contém um tipo genérico, dessa forma temos segurança nos tipos dos dados e evitamos espalhar "casting" pelo código. Enfim, vamos ao nosso exemplo do botão com uma Task do tipo String:
EventHandlercenario4 = e -> { carregando.setVisible(true); Task tarefaCargaPg = new Task () { @Override protected String call() throws Exception { return carregaPagina(txtUrl.getText()); } @Override protected void succeeded() { String codigo = getValue(); carregando.setVisible(false); txtResultado.setText(codigo); } }; Thread t = new Thread(tarefaCargaPg); t.setDaemon(true); t.start(); };
Ótimo! Veja nossa aplicação final abaixo. Para deixar tudo mais legal, colocamos um ProgressIndicator, assim quando a página está sendo carregada, a opção de carregar nova página fica também desabilitada.
Conclusão
Mostramos como fazer atividades em paralelo no JavaFX não é um bicho de 7 cabeças.O nosso código está no github!