Ejemplo de Swing Worker: ¿Por qué se me congela la interfaz?

8
24632

Ejemplo de Swing Worker: ¿Por qué se me congela la interfaz?

0. Índice de contenidos.

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 17″ (2,6 Ghz Intel Core i7, 8 GB DDR3)
  • Sistema Operativo: Mac OS X Snow Leopard 10.6.4
  • Maven 2.2.1
  • Eclipse 3.6 (Helios) con M2Eclipse
  • jdk 1.6

2. Introducción

Una de las tareas más comunes que tenemos que hacer cuando desarrollamos con Swing es la de actualizar la interfaz en función de los procesos que se van produciendo. En muchas ocasiones nos encontramos con la sorpresa de que la interfaz no se actualiza en el momento, si no que parece congelarse y sólo acaba actualizándose cuando el proceso finaliza.

La explicación a este fenómeno es sencilla, Swing todos los eventos relacionados con la interfaz los gestiona como una cola FIFO en un hilo independiente, conocido con el nombre de EDT (Event Dispatching Thread). Por tanto, si nosotros ejecutamos tareas pesadas dentro del radio de actuación de este hilo, estaremos bloqueando el resto de peticiones de actualización de la interfaz.

Para evitar bloquear el hilo EDT, Swing nos proporciona una clase llamada SwingWorker, que lo que hace es abrir otro hilo para ejecutar la tarea pesada, y así no bloquear el EDT.

3. Para muestra un botón… o mejor dos

Para demostrar este funcionamiento vamos a crear un proyecto donde vamos a tener un pantalla con un campo de texto y dos botones. El primer botón va a ejecutar su acción sin la utilización de Swing Worker y el segundo si va a utilizar Swing Worker.

Este es el código para la ventana comentada:

package com.autentia.ejemplo;

import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

@SuppressWarnings("serial")
public class EjemploFrame extends JFrame {
	
	public static int MAX_ITE = 100000;
	
	private JTextField jtfInformacion = new JTextField();
	private JButton jbIncrementarSinSwingWorker = new JButton();
	private JButton jbIncrementarConSwingWorker = new JButton();
	private JPanel jpMarco = new JPanel();
	private JPanel jpAccciones = new JPanel();
	
	public EjemploFrame(){
		initComponents();
	}
	
	private void initComponents(){
		jpMarco.setLayout(new BorderLayout());
		jpMarco.add(jtfInformacion, BorderLayout.NORTH);
		

		jbIncrementarSinSwingWorker.setAction(new IncrementarSinSwingWorkerAction(this));
		jbIncrementarSinSwingWorker.setText("Incrementar sin swing worker");
		jbIncrementarConSwingWorker.setAction(new IncrementarConSwingWorkerAction(this));
		jbIncrementarConSwingWorker.setText("Incrementar con swing worker");
		
		jpAccciones.add(jbIncrementarSinSwingWorker);
		jpAccciones.add(jbIncrementarConSwingWorker);
		
		jpMarco.add(jpAccciones, BorderLayout.CENTER);
		
		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		
		getContentPane().add(jpMarco);
		
		this.setTitle("Ejemplo funcionamiento Swing Worker");
		
		pack();
	}
	
	public JTextField getTextField(){
		return this.jtfInformacion;
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable(){
			public void run(){
				new EjemploFrame().setVisible(true);
			}
		});
	}
}

Si nos fijamos en el método main de esta clase, veremos la utilización de la clase SwingUtilities, más concretamente el método invokeLater. Este método precisamente lo que hace es que el proceso que implementa se encole en la lista de eventos pendiente del hilo EDT. Una buena práctica es que todos los métodos que afecten a la interfaz gráfica, se ejecuten dentro de un invokeLater, para asegurarnos de que la gestión del hilo EDT se está haciendo correctamente.

La acción que ejecutan los dos botones va a ser la misma, ambos van a mostrar en el cuadro texto la cuenta ascendente de una variable hasta que se iguale al valor de la constante MAX_ITE.

En primer lugar vamos a desarrollar la acción sin el uso de Swing Worker:

package com.autentia.ejemplo;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;

@SuppressWarnings("serial")
public class IncrementarSinSwingWorkerAction extends AbstractAction{
	
	private EjemploFrame ejemploFrame;
	
	public IncrementarSinSwingWorkerAction(EjemploFrame ejemploSinSwingWorkerFrame){
		this.ejemploFrame = ejemploSinSwingWorkerFrame;
	}

	public void actionPerformed(ActionEvent arg0) {
		int ite = 0;
		while (ite < EjemploFrame.MAX_ITE){
			ite = ite + 1;
			this.ejemploFrame.getTextField().setText("" + ite);
		}
		
	}	
}

Si ejecutamos esta acción veremos el efecto del que hablábamos en la introducción, el botón se va a quedar como congelado, hasta que el proceso finaliza y muestra directamente el valor de MAX_ITE, sin mostrar realmente el conteo ascendente. Esto es porque la tarea está bloqueando el hilo EDT.

Para evitar esto la acción del otro botón la vamos a implementar utilizando Swing Worker, el código quedaría de esta forma:

package com.autentia.ejemplo;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.SwingWorker;

@SuppressWarnings("serial")
public class IncrementarConSwingWorkerAction extends AbstractAction{
	
	private EjemploFrame ejemploFrame;
	
	public IncrementarConSwingWorkerAction(EjemploFrame ejemploSinSwingWorkerFrame){
		this.ejemploFrame = ejemploSinSwingWorkerFrame;
	}

	public void actionPerformed(ActionEvent arg0) {
		final SwingWorker worker = new SwingWorker(){

			@Override
			protected Object doInBackground() throws Exception {
				int ite = 0;
				while (ite < EjemploFrame.MAX_ITE){
					ite = ite + 1;
					ejemploFrame.getTextField().setText("" + ite);
				}	
				return null;
			}	
		};
		worker.execute();
	}
}	

Si ahora ejecutamos la segunda acción veremos que el campo se actualiza casi secuencialmente hasta que llega al valor de MAX_ITE. Con lo que el problema queda resuelto, ya que la tarea se ejecuta en un hilo independiente y no bloquea el EDT. Otra forma de resolver el problema es crear nosotros mismos el hilo con la interfaz Runnable, pero si ya existe una forma estándar de hacerlo, mejor usamos la clase Swing Worker.

4. Conclusiones

Con esto espero haber ayudado a todos aquellos a los que una vez no supimos porque nuestra aplicación Swing no se comportaba como nosotros esperábamos.

Saludos.

8 COMENTARIOS

  1. Hilos y sincronización… en un proyecto de Telefónica tuvimos que pelearnos mucho con esto (dos semanas nos llevó averiguarlo y corregirlo). PD: los hilos en Java depende mucho del SO..

  2. Hola, me queda la duda del ejemplo:
    cuando se oprime el boton y se ejecuta el codigo de IncrementarSinSwingWorkerAction y desde alli se hace
    ejemploFrame.getTextField().setText(\\\»\\\» + ite); ¿esta ultima linea no debería hacerse nuevamente con invokeLater para que sea ejecutado por el hilo EDT?

    Saludos,
    Juan

  3. Muchas gracias me ha servido de mucho este tutorial, bastante semanas que estuve buscando eesta solución y no la encontraba =D, algo tan sencillo me estaba dando dolor de cabeza lo que son las cosas jaja

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad