Swingworker! again!

oi pessoal…
há um tempo atras eu perguntei como implementar o SwingWorker e o doInBackground, mas não consegui entender realmente as perguntas, e como faltava terminar o projeto, deixei quieto e fui terminando tudo.
pois bem, o projeto está pronto, e unica coisa que falta é exatamente isso. Eu entendi a teoria, e como funciona, mas não faço ideia de como implementar isso num projeto mais complexo, com varios pacotes e tal… todos os exemplos que eu vejo pela internet são simplistas demais para ajudar…
o fato do método doInBackground não receber parâmetros também me complica… eu tenho que abrir esse método, e dele instanciar métodos e classes para que funcione?
espero que consigam me ajudar, que estou começando a ficar desesperado… =/
abraço

edit: mais algumas perguntas:
se eu chama o método doInBackground, tudo que eu fizer ligado a esse método e dentro dele, mesmo em outras classes, será na outra thread?
como faço pra testa em qual thread o programa está trabalhando?

Cara, há N formas de usar o SwingWorker.
Como essa a seguir.

	class MySwingWorker extends SwingWorker<Object, Object> {
		public int number;
		public String path;

		@Override
		protected Object doInBackground() throws Exception {
			// faz alguma operacao, como um acesso a banco
			return operationFacade.save(number, path);
		}

		@Override
		protected void done() {
			try {
				// este get vai retornar o resultado do doInBackground, por
				// exemplo o retorno do acesso ao banco. Com ele podemos 
				// atualizar a tela
				Object obj = get();
				wizardEnd.getJLabel0().setText(
						"Operacao concluida com sucesso. " + obj);
			} catch (Exception e) {
				e.printStackTrace();
			}
			progressBarPanel.setProgressBar(false);
		}
	};

	private void event(int number, String path) {
		progressBarPanel.setProgressBar(true);
		MySwingWorker worker = new MySwingWorker();
		worker.number = number;
		worker.path = path;
		worker.execute();
	}

Pense que o método event() é disparado por um actionListener, um botão.
Ele então instância um MySwingWorker, define alguns parametros e dispara
o SwingWorker chamando .execute().

O SwingWorker então chama o doInBackground() numa thread que não irá congelar
a tua tela e retorna alguma coisa. Um processo, relatórios, acesso a banco, etc.

Após o SwingWorker chama o done(), que é executado no EDT (sabes o que é?).
A função dele é pegar o retorno do processo, através do get(), e fazer alguma
atualização na tela de acordo.

Mas se quiser, não precisar criar o MySwingWorker, basta fazer algo assim:

	private void event(int number, String path) {
		progressBarPanel.setProgressBar(true);
		new SwingWorker<Object, Object>() {
			@Override
			protected Object doInBackground() throws Exception {
				// faz alguma operacao, como um acesso a banco
				// number e path estao acessiveis na classe
				return operationFacade.save(number, path);
			}
			
			@Override
			protected void done() {
				try {
					// este get vai retornar o resultado do doInBackground, por
					// exemplo o retorno do acesso ao banco. Com ele podemos 
					// atualizar a tela
					Object obj = get();
					wizardEnd.getJLabel0().setText(
							"Operacao concluida com sucesso. " + obj);
				} catch (Exception e) {
					e.printStackTrace();
				}
				progressBarPanel.setProgressBar(false);
			}
		}.execute();
	}

É isto, não precisa fazer mais nada, como chamar o doInBackground, o done, controlar Thread
ou qq coisa. Basta criar o SwingWorker, preencher os métodos e chamar o .execute()

Ou seja, experimente as opções e fique com aquele que achar melhor!

[quote=luisera]…e como faltava terminar o projeto, deixei quieto e fui terminando tudo.
pois bem, o projeto está pronto, e unica coisa que falta é exatamente isso.
[/quote]
Ok, então agora chegou a hora de implementar, pois se não fizer isso o Swing vai ficar congelando,
colaborando assim com a fama que java é lento.

Na mensagem anterior tem dois exemplos de como passar parâmetros para o SwingWorker.

Sim, vc pode instanciar outras classes ou fazer ali mesmo. Minha sugestão é que esse método chame
algo como um Facade. Não precisa ter lógica nele, apenas disparar alguma coisa.

[quote=luisera]
se eu chama o método doInBackground, tudo que eu fizer ligado a esse método e dentro dele, mesmo em outras classes, será na outra thread?
como faço pra testa em qual thread o programa está trabalhando?[/quote]
Sim, a partir do doInBackground vc está em outra Thread. Mas cuidado, não precisa chamar ele explicitamente, o SwingWorker
que chama a partir do SwingWorker.execute().
Sobre a Thread em que o programa está trabalhando, se é algum evento de um componente, como um ActionListener
do botão, então é na EDT.

obrigado pela ajuda…
fiz parecido com o primeiro jeito que o rapaz ali emcima falou, criei uma classe Executar extends SwingWorker dentro da minha classe principal do programa… ai depois que o usuário escolhe os argumentos na GUI e clica ‘calcular!’ eu crio um objeto exec dessa classe e do um exec.Execute(); …
parece estar funcionando, mas eu queria testar tudo certinho, verificar que todos os cálculos estão em outra thread, como faço isso?
uma coisa interessante é aquela progressbar que foi colocado nos exemplos, mas não tem que ficar atualizando ela?
no fim nem usei o método done…
obrigado novamente… =)

[quote=luisera]obrigado pela ajuda…
fiz parecido com o primeiro jeito que o rapaz ali emcima falou, criei uma classe Executar extends SwingWorker dentro da minha classe principal do programa… ai depois que o usuário escolhe os argumentos na GUI e clica ‘calcular!’ eu crio um objeto exec dessa classe e do um exec.Execute(); …
parece estar funcionando, mas eu queria testar tudo certinho, verificar que todos os cálculos estão em outra thread, como faço isso?
uma coisa interessante é aquela progressbar que foi colocado nos exemplos, mas não tem que ficar atualizando ela?
no fim nem usei o método done…
obrigado novamente… =)[/quote]

Se os calculos foram chamados a partir do doInBackground estão em outra Thread.

Sobre o progressBar, aquele é um específico nosso aqui. Mas pode te dar uma ideia de como
usar. Sobre ficar atualizando ele, se for um que fica girando ou algo assim não precisa
atualizar. Mas se for um que dá porcentagem, ou status vc pode usar o publish do SwingWorker
para atualizá-lo.

os cálculos não estão dentro do método doinbackground, mas estão indiretamente nele…
novamente, como faço pra testar? porque é uma coisa muito abstrata, e sei que minha habilidade pra fazer coisas erradas parecem certas é enorme. =)
gostei dessa ideia de algo girando… não lembro de ter visto isso em nenhum swing componente… como é?

Chame esse método para saber se está na EDT

javax.swing.SwingUtilities.isEventDispatchThread()

Aproveita e estude 2 métodos interessantes. Se num determinado momento quiser
executar algo na EDT, chames eles:

SwingUtilities.invokeAndWait(..);
SwingUtilities.invokeLater(..);

Sobre o progressBar girando, é só criar um gif animado e colocar na tela!

cabei de falar com meu chefe… ele gostou da ideia do progressbar, mas de uma verdadeira, com a porcentagem… não faço ideia de como calcular isso… =/

desculpa ressuscitar o tópico, mas eu fui lá no javadoc e vi que a classe swingworker tem um método “cancel”… aí fui utilizá-lo, mas não estou conseguindo…
como dito acima, minha classe que extends o swingworker chama-se Executar e seus objetos exec. Essa classe está dentro da classe principal que contem a GUI.
do jeito que estou fazendo, estou nomeando um objeto Execute exec; na classe principal e depois quando necessário faço exec.execute…
aí quando o usuário clica “abortar” faço exec.cancel(true); e depois coloquei até if(exec.isCancelled()) System.out.println(“cancelou!”);…
o cancelou aparece, mas é só eu esperar um pouco que o programa completa e retorna, demonstrando que a execução não foi cancelada como deveria…
estou perdido…
obrigado antecipadamente.
abraços

help, please! não estou entendendo mais nada!

Estou com o mesmo problema. O meu até cancela, mas nunca mais executa novamente, só se eu fechar a aplicação e abrir de novo.

Se descobrir como fazer para cancelar, posta aqui.

O seu método doInBackground() tem de checar, através do método isCancelled(), se foi solicitado o cancelamento da tarefa. Há um exemplo para isso na seção “Sample Usage” da classe SwingWorker:

http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html

Para solicitar o cancelamento da tarefa, pode-se usar o método cancel() do SwingWorker:

http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html#cancel(boolean)

Ao usar cancel(false), o SwingWorker NÃO vai forçar a interrupção da thread de trabalho do SwingWorker, ou seja, é tarefa do programador checar dentro do método doInBackground() se o cancelamento foi solicitado, usando isCancelled(). Ao usar cancel(true), o SwingWorker vai derrubar INCONDICIONALMENTE a thread de trabalho, garantindo cancelamento imediato. Prefiro usar cancel(false), porque permite que você realize processamento específico (por exemplo, liberar recursos e atualizar a Interface Gráfica) quando um usuário solicitar um cancelamento.

Pra terminar, se vocês querem executar um SwingWorker novamente, é preciso criar uma nova instância dele. Instâncias de SwingWorker, depois de executadas, não podem mais ser reaproveitadas.

Ok. Fiquei com medo de ficarem várias instâncias rodando e ocupando memória.

Fiz aqui e funcionou.

Valeu pessoal.

Estranho, o cancel() não está parando a thread.

No doInBackground, faço a verificação para saber se está cancelado, mas mesmo assim, as instâncias que cancelo continuam executando. O que está errado?

   workerCaract = new SwingWorker() {

          @Override
          protected Object doInBackground() throws Exception {
             
             if (!isCancelled()) {
                ProgressBar2.setString("Carregando...");
                ProgressBar2.setStringPainted(true);
                ProgressBar2.setIndeterminate(true);
                buscaCaract();
             }
             else {
                done();   
             }
             
             return null;
          }
          
          @Override
          protected void done() {
             
             if (isCancelled()) {
                DefTableModel dtm = (DefTableModel) tblPecCaract.getModel();
                for (int i = tblPecCaract.getRowCount() -1; i > -1; i --)
                    dtm.removeRow(i);                     
             }                
             
             ProgressBar2.setIndeterminate(false);
             ProgressBar2.setString("Peças: " + tblPecCaract.getRowCount());
          }
       };
   workerCaract.execute();    

No método doInBackground() você precisa checar continuamente se o cancelamento foi solicitado, e no seu código você só está fazendo isso uma única vez. Se a solicitação for realizada somente depois que você chamou isCancelled(), você não terá como saber que o cancelamento foi solicitado, e o SwingWorker não fará o cancelamento automaticamente por você.

Você diz pra eu colocar um while no lugar do if?

Mais ou menos isso. É interessante colocar um laço (while, for, etc.) e checar a cada iteração se o cancelamento foi solicitado, mas apenas se a sua tarefa demorada for divisível. Se ela for indivisível - o que me parece ser o caso, já que é toda feita numa única chamada a buscaCaract(), em vez de várias chamadas sucessivas - não há como inserir checagens periódicas a isCancelled(). Nesse caso, pode ser interessante derrubar incondicionalmente o SwingWorker.

Mas eu já faço isso: worker.cancel(true).

É justamente o que eu quero, derrubar de qualquer jeito. Mas a tarefa continua rodando.

Que problemão :confused: Você já verificou o retorno da chamada a worker.cancel(true)? Ela retorna true se o SwingWorker foi interrompido com sucesso, e false caso contrário. (Pegadinha: se o SwingWorker já tiver sido concluído, o retorno será false.)

Veja se estou fazendo certo:

Eu tenho uma variável privada estatica do tipo SwingWorker no meu JDialog.

No click de um botão, eu faço essa variável receber uma nova instância, do jeito que te mandei no código.

No click de outro botão eu faço, worker.cancel(true).

O cancel está retornando true.

Estava pensando aqui, esse new que eu faço ao clicar o botão deve criar novos objetos certo? Será que eu estou cancelando uma instância e deixando as outras continuarem?