Sexta-feira, Julho 06, 2007

Swing Application Framework (JSR-296) - Parte 3

A terceira parte do artigo sobre o Swing Application Framework trata sobre a execução de tarefas em background.

A primeira parte discorre sobre a JSR-296, gerenciamento de ciclo de vida e inicialização do aplicativo, o armazenamento do estado da GUI entre sessões e persistência local. A segunda parte apresentou a injeção de recursos e a definição, gerenciamento e binding de Action’s.

Para uma total compreensão dessa terceira parte, é necessária a leitura da segunda parte do artigo e do texto O Básico sobre SwingWorker.

Tarefas em Background

Task estende SwingWorker, o que equivale a dizer que representa uma tarefa que executa em [uma thread em] background. De acordo com sua documentação, Task adiciona [à SwingWorker]:

  • uma série de propriedades descritivas que podem ser mostradas aos usuários;
  • um novo conjunto de métodos para customizar o término de uma tarefa;
  • possibilidade de bloquear entrada (input) à GUI enquanto a tarefa está executando; e
  • um TaskListener que permite monitorar os três métodos chaves de SwingWorker: doInBackground, process e done.

Done

O método abstrato doInBackground é a principal questão a ser considerada ao implementar tanto uma subclasse de SwingWorker quanto de Task, já que é sua responsabilidade definir exatamente o que irá ser executado em background.

O método done, porém, é final em Task. done é utilizado para executar atividades complementares na event dispatch thread (EDT) após o termino do método doInBackground. Em Task, done invoca os métodos succeeded, cancelled, interrupted ou failed quando a worker thread termina, respectivamente, com sucesso, porque foi cancelado, interrompido ou por alguma falha de execução.

Independentemente do resultado, done sempre invoca finished ao terminar sua execução, mesmo que uma exceção seja lançada. Por padrão, failed grava o erro em questão num arquivo de log; os demais métodos nada fazem. Como done executa na EDT, todos esses métodos também correrão nessa mesma thread.

Task in Action

Como mostrado em O Básico sobre SwingWorker, obter as entidades cadastradas num banco de dados dificilmente deve ocorrer na EDT, sob pena de congelamento da GUI. Aquele artigo mostra o uso de SwingWorker para resolver essa situação, enquanto esse texto irá mostrar o mesmo com Task, por suas facilidades adicionais.

private class LoadEntitiesWithTask extends Task<List<EntityClass>, Void> {

.....LoadEntitiesWithTask() {
..........super();
..........setLoadEnabled(false);
..........setStopEnabled(true);
.....}

.....protected List<EntityClass> doInBackground() {
..........setMessage("Obtendo " + entityName + "s cadastrados...");
..........return domainManager.findAll();
.....}

.....protected void succeeded(final List<EntityClass> newEntities) {
..........synchronized(this) {
...............if (!entities.isEmpty()) {
....................clear();
...............}
...............entities.add(0, EMPTY_ENTITY);
...............entities.addAll(newEntities);
..........}
..........fireContentsChanged(this, 0, entities.size() - 1);
..........setMessage(
...................."Feito: lista de " + entityName + "s obtida com êxito em "
....................+ getExecutionDuration(TimeUnit.MILLISECONDS) + "ms.");
.....}

.....protected void cancelled() {
..........setMessage("Operação cancelada.");
.....}

.....protected void interrupted(final InterruptedException ex) {
..........setMessage("Operação cancelada.");
.....}

.....protected void failed(final Throwable ex) {
..........setMessage("Erro: não foi possível obter " + entityName + "s cadastrados.");
.....}

.....protected void finished() {
..........setLoadEnabled(true);
..........setStopEnabled(false);
.....}
}

As mudanças mais significativas em relação à sua equivalente com SwingWorker são referentes aos métodos succeeded (no lugar de done), cancelled, interrupted, e failed que enviam mensagens a componentes específicos, como statusbar (mais sobre isso depois), e ao método finished.

Task pode ser utilizada em conjunto com @Action's (sobre Action's, ver parte 2) para definir tarefas que iniciam a partir de eventos gerados pela GUI.

@Action(block=BlockingScope.ACTION, enabledProperty="loadEnabled")
public Task load() {
.....loadEntitiesTask = new DefaultEntityModel.LoadEntitiesWithTask();
.....return loadEntitiesTask;
}

Note que o método load retorna Task. Infelizmente, tanta facilidade tem seu preço. Para alterar o comportamento de LoadEntitiesWithTask de modo que apenas obtenha as entidades se as mesmas não foram previamente carregadas, é preciso estender a classe. (Com SwingWorker, bastaria criar uma variação do método load).

private class LoadEntitiesIfEmpty extends LoadEntitiesWithTask {

.....LoadEntitiesIfEmpty() {
..........super();
.....}

.....protected List<EntityClass> doInBackground() {
..........if (entities.isEmpty()) {
...............return super.doInBackground();
..........}
..........return null;
.....}

.....protected void succeeded(final List<EntityClass> entities) {
..........if (entities == null) {
...............return;
..........}
..........super.succeeded(entities);
.....}
}

O método loadIfEmpty é responsável, sob o ponto de vista de quem o invocar, de carregar as entidades no formulário caso isso ainda não tenha acontecido.

public void loadIfEmpty() {
.....loadEntitiesTask = new DefaultEntityModel.LoadEntitiesIfEmpty();
.....ApplicationContext.
...............getInstance().
...............getTaskService().
...............execute(loadEntitiesTask);
}

Bloqueando a GUI

Ao utilizar a worker thread para executar longas tarefas, pode ser necessário bloquear certos componentes ou o aplicativo inteiro até que a tarefa termine.

Como exemplo, a tarefa de recarregar as entidades cadastradas num banco de dados certamente deve correr em background, e a possibilidade de concorrência dessa mesma tarefa dificilmente teria alguma utilidade. Portanto, bloquear o componente que inicia a tarefa em questão até que termine é interessante.

A opção de bloquear a GUI durante a execução de uma tarefa é feita definindo um valor para a propriedade block da anotação Action. Os possíveis valores para block são obtidos através da enumeração BlockingScope de Task: NONE (o padrão), ACTION, COMPONENT, WINDOW e APPLICATION. O método load, acima, utiliza BlockingScope.ACTION:

@Action(block=BlockingScope.ACTION, . . .)
public Task load() {
...... . .
}

Repare que o botão para carregar as entidades está desabilitado enquanto LoadEntitiesWithTask executa (observe a barra de progresso na statusbar).

BlockingScope.WINDOW e BlockingScope.APPLICATION bloqueiam a janela de nível mais alto e o aplicativo inteiro, respectivamente, por padrão mostrando um dialog indicando que o software está ocupado. O modo como esse bloqueio é feito pode ser customizado implementando a classe abstrata application.Task.InputBlocker e estabelecendo-a como o bloqueador de Task por meio de seu método setInputBlocker.

You've Got a Message

Com o auxílio de application.TaskMonitor, desenhada para ser o model de widgets como barra de estado, e dos métodos de Task setMessage e message, enviar notificações para a GUI sobre o estado da execução das tarefas na worker thread beira o trivial.

setMessage foi utilizado por simplicidade. O modo aconselhável para isso é definir as mensagens num arquivo de propriedade e utilizar o método message (consulte documentação da classe e exemplos que acompanham o framework).


Repare na mensagem avisando sob o término da operação. Além disso, como a operação para carregar as entidades chegou ao fim. o botão para carregar as entidades está habiltado, enquanto o que cancela essa operação está desativado...

Outros Métodos Úteis

A classe Task ainda possui uma série de métodos úteis que incluem, mas não se limitam a:
  • getExecutionDuration retorna o tempo de execução da tarefa;
  • setProgress tem o objetivo de notificar barras de progresso em statubars que a tarefa teve algum progresso;
  • isStarted e isPending complementam os métodos, presentes na classe SwingWorker, isDone e isCancelled.

Mantenha em mente que...

Esse artigo é apenas uma introdução ao assunto. Não deixe de consultar a documentação e até mesmo o código-fonte do projeto, fazer experiências, discutir sobre as funcionalidades do framework.

Veja também:
Fontes e Referências utilizadas:

8 comentários:

Rafael Fiume disse...

Atenção: A API do Swing Application Framework está sujeita a mudanças, portanto o artigo pode encontrar-se fora de sincronia com a API mais recente. Ou seja, desatualizado.

O ModelMat utiliza o Swing Application Framework, portanto é possível consultá-lo como exemplo de aplicação desse framework.

Também é aconselhável estudar os exemplos contidos nas fontes do Swing Application Framework.

Giba disse...

Rafael, sei que faz muito tempo deste post, mas gostaria de saber se existe algum lugar que posso acessar a segunda parte do mesmo, pois o blog de referencia está fora do ar.

Rafael Fiume disse...

Giba, tudo certo?

Realmente está fora do ar, e eu não previ esse problema. Fica de lição...

Mas você pode ver uma excelente documentação sobre o assunto aqui.

[]'s!

Giba disse...

Rafael, obrigado ! Vou dar uma olhada na documentação. Só mais uma pergunta vi que no java 7 a swing application framework vai sair fora, você acha que vale a pena desenvolver um software sobre essa framework ainda ? Sei que muito se fala de JavaFX, mas acho uma boa framework e vi que você já utilizou bastante. O que você tem a dizer ? E com relação ao OpenSwing, você ja usou ?

Desde já agradeço sua atenção!

[]s

Rafael Fiume disse...

Giba, mexer com threads não é algo fácil, muito menos em conjunto com Swing. O pior projeto de todos que lidei era um ATM para uma grande rede varejista completamente furado! Os desenvolvedores simplesmente não tinham noção de threads, nem de Swing, que dirá de ambos. O sistema às vezes travava de uma tal forma, que precisávamos reiniciar o sistema operacional.

Cheguei no final do projeto, quando já estava atrasado e precisava fazer o negocio funcionar. Precisávamos refazer tudo (o que não aconteceu, o projeto fracassou e a empresa perdeu o cliente).

Se tivessem utilizado SwingWorker - que é a base pro SAF - já estaria ótimo. Fenomenal! Além de ajudar a lidar com multithreads com Swing, essa classe faz parte do JDK e auxilia bastante durante o design do software. Tanto que nesse projeto, não conseguiamos encaixar a SwingWorker (e por isso precisávamos refazer tudo). Eu ainda não usaria o SAF, devido à confusão reinante (coisa muito triste).

Porém, pode até valer a pena caso seja necessário utilizar boa parte dos recursos da JSR-296. Se vocês encontrarem-se implementando as funcionalidades já existente no Swing Application Framework, com certeza será melhor utilizá-lo do que usar a versão caseira. E é preciso deixar bem claro que 1) vocês terão que fazer a manutenção desse framework, ou 2) terão que fazer o refactoring quando finalmente lançarem a API final (sem previsão).

Não conheço o OpenSwing, mas conheço o SwingX. Acho o SwingX mais confiável por ser do SwingLabs, que é patrocinado pela Sun. Mas a compatibilidade dessa API não é garantida.

Depois me fale sobre os resultados do seu projeto.

[]'s.

Giba disse...

Ok, muito obrigado mesmo por sua dicas, vou usar o SAP mesmo o aplicativo não é tão grande assim, e depois lhe mando informações sobre como foi o desenvolvimento do mesmo.

Giba disse...

Rafael, boa noite !

Desculpe lhe incomodar novamente, mas deixa eu ver se pode me ajudar ?Esse programa(imagens) que você demonstra nos exemplos do SAP e do SWINGX é um aplicativo comercial, ou seu de estudo ? Se for de estudo tem como disponibiliza-lo ? Queria montar uma estrutura parecida com a que você montou ai, onde cada um dos links a esquerda abri-se uma nova tela do lado direito. Você pode me ajudar ? Estou usando uma FrameView do SAP, para montar a tela principal, mas não sei se é a melhor opção

Rafael Fiume disse...

Não é comercial, era algum experimento que fiz pra um tutorial que estava preparando na época. Mas isso já faz mais de 3 anos, e não sei onde esse código foi parar.

Certeza que no sítio do SwingX existe um demo mostrando todas as funcionalidades dos seus widgts junto com seus respectivos código-fonte.