domingo, 16 de octubre de 2011

Interfaces de Usuario Swing con Hilos

En esta entrada quiero compartir algunos problemas de subprocesamiento multiples  (ejecución de tareas en paralelo) relacionados con la programacion de interfaces graficas de usuarios.

Hilos en Swing
Una de las razones de usar hilos en tus aplicaciones es la de hacer que tu aplicacion sea mas interactivo. Cuando tu aplicacion realiza una tarea que requiere mucho tiempo, más de dos segundos a mi juicio, entonces debes ejecutar la tarea en otro hilo para evitar que la interfaz de usuario se quede "congelada".

Hay un hilo diseñado exclusivamente para pintar la interfaz grafica y para procesar los eventos del usuario, este hilo es conocido como el EDT (Event Dispatch Thread), todo código que requiera manipular componentes swing debe ser ejecutado en este hilo, de no hacerlo así te puedes llevar varias sorpresas. Swing es not thread safe. Es decir, la mayoria de los metodos de las clases Swing no son sincronizados. Si tu intentas manipular los elementos de la interfaz grafica del usuario desde varios hilos entonces la interfaz de usuario se dañara.

Evolución del Multiprocesamiento en Java
Me inicié en el mundo Java por los años 2008, han pasado ya casí 10 años y he visto como Java ha ido creciendo y evolucionando para facilitarle las tareas al programador. Antes de la versión 1.6, para ejecutar tareas en paralelo tenías que extender explicitamente a la clase Thread y programar toda la lógica del subprocesamiento multiple, sincronización, interacción con la GUI y demás. Con la llegada de la versión 1.6 aparece una clase llamada SwingWorker el cual se encarga de manejar toda la logica del subprocesamiento en paralelo, facilitandote en mucho la programación.

Resumiendo, hay dos formas de programar el subprocesamiento multiple con Swing.
  1. La forma tradicional en la que extiendes a Thread y programas toda la lógica de hilos.
  2. Extendiendo la clase SwingWorker

Yo recomiendo la segunda, usando la clase SwingWorker.
Para profundizar en el hilo EDT y SwingWorker puedes leer mi entrada SwingWorker y el Hilo que despacha los Eventos ahí explico detalladamente estás dos clases.

Problemas más comunes con sub procesamiento multiple

Por ejemplo corre el siguiente programa.

package com.maro.gui;

import java.util.Random;

import javax.swing.JComboBox;

public class BadWorkerThread extends Thread {

public BadWorkerThread(JComboBox aCombo){
combo = aCombo;
generator = new Random();
}
public void run(){
try {
while (!interrupted()){
int i = Math.abs(generator.nextInt());
if(i % 2 == 0){
combo.insertItemAt(new Integer(i), 0);
} else if(combo.getItemCount() > 0){
combo.removeItemAt(i % combo.getItemCount());
}
sleep(1);
}
} catch(InterruptedException iee){
}
}
private JComboBox combo;
private Random generator;
}


Un frame para visualizarlo, una vez que corras el programa presiona el boton "Bad" y juega con el combo box, notaras que el compilador lanza varias excepciones.

package com.maro.gui;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestBadWorkerThread extends JFrame{

private JComboBox combo;
private Thread longTask;
public TestBadWorkerThread (){
super("Prueba");
combo = new JComboBox();
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new FlowLayout());
JButton badButton = new JButton("Bad");
badButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
longTask = new BadWorkerThread(combo);
longTask.start();
}
});
contentPanel.add(badButton);
contentPanel.add(combo);
this.getContentPane().add(contentPanel);
this.pack();
//this.setVisible(true);
}
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// TODO Auto-generated method stub
TestBadWorkerThread pr = new TestBadWorkerThread();
pr.setVisible(true);
}
});
}
}

Graficos y Java 2D : El contexto grafico

Un contexto de graficos en java es un objeto que nos permite dibujar en pantalla, dibujar pixeles en la pantalla que representan texto y otros objetos graficos (como lineas, elipses, rectangulos y otros poligonos). El contexto grafico tambien nos proporciona y nos permite manipular otro conjunto de informacion, como el color de fondo, el font a usar, etc,. La clase Graphics de java es un contexto de graficos, encapsula toda la informacion requerida para dibujar, ademas nos permite dibujar graficos (como lineas, elipses, rectangulos y poligonos).

La clase Graphics es una clase abstracta (es decir, no pueden instanciarse objetos Graphics). Esto contribuye a la portabilidad de java. Como el dibujo se lleva a cabo de diferente manera en cada plataforma que soporta a java, no puede haber una accion de implemento de las herramientas de dibujo en todos los sistemas, es decir, las herramientas usadas para dibujar graficos en windows son distintas a las usadas en linux, o en mac. Cada plataforma tiene su implementacion de la clase Graphics y esta implementacion esta oculta para nosotros por medio de la clase Graphics.

La funcion paint y update nos suministran el contexto grafico del applet o del componente, en otros casos, hemos de obtener el contexto grafico del componente mediante la función getGraphics.Una vez obtenido el contexto grafico podemos llamar desde este objeto a las funciones graficas definidas en la clase Graphics.

public void paint(Graphics g){
   //usar el contexto gráfico g 
}
public void update(Graphics g){ 
   //usar el contexto gráfico g 
}
void funcion(){     
   Graphics g=getGraphics();     
   //usar el contexto gráfico g     
   g.dispose(); 
}

Como vemos en esta porcion de codigo existe una sutil diferencia entre suministrar y obtener el contexto grafico g. Solamente es necesario liberar los recursos asociados al contexto g, mediante la llamada a la función dispose, cuando se obtiene el contexto grafico mediante getGraphics.