Este post pertenece a mi antiguo blog en wordpress, pero decidí pasarme a github:pages por las siguientes razones
SwingWorker
es una clase de utilidad incluida en Java SE 6 que soluciona el problema de
utilizar un hilo (thread) separado al de Swing para realizar tareas de larga duración.
Anteriormente ya existían implementaciones que realizaban esta tarea, pero en la versión
6 de Java SE se ha decidido incluirla en la propia distribución de Java. Existe un
backport de SwingWorker
para utilizar
los mismos conceptos en Java SE 5. Esta entrada es aplicable tanto para SwingWorker
incluido en Java SE 6 como para el backport.
El hilo manejador de eventos (Event Dispatching Thread)
Durante el desarrollo de interfaces gráficas suelen aparecer tareas cuya realización conllevan mucho tiempo, por ejemplo, leer un archivo XML cuando el usuario pulse un botón. La forma más rápida y sencilla de realizar esta tarea podría ser los siguiente:
JButton button = new JButton("Cargar fichero XML");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
xmlDocument = loadXML();
}
});
De esta forma, la carga del archivo se realiza en el hilo manejador de eventos (EDT - Event Dispatching Thread) de Swing, y como la tarea necesita mucho tiempo, no es posible manejar el resto de eventos que la interfaz gráfica genera y se quedara congelado, fijo, sin respuesta aparente, hasta que la tarea finalice. Esto produce un efecto indeseable de cara al usuario de la aplicación.
De hecho, a la hora de desarrollar interfaces gráficas se deberían respetar estas dos reglas básicas:
- Las tareas de larga duración no deben ejecutarse nunca en el hilo manejador de eventos (EDT), de otra forma la aplicación no responderá al usuario
- Los componentes gráficos se deben acceder únicamente desde el hilo manejador de eventos
Cualquier otra debería realizarse en un hilo separado (difícil pero es lo más recomendable).
Solución
Efectivamente, la solución pasa por utilizar un hilo distinto al EDT. SwingWorker
nos
proporciona la base para utilizar un hilo distinto para realizar tareas costosas en
cuanto al tiempo de ejecución. SwingWorker
permite ejecutar tareas de larga duración
mientras la interfaz gráfica sigue respondiendo a la interacción con el usuario.
La forma más simple de utilizar SwingWorker
es implementando el método doInBackground
extendiendo la clase SwingWorker
o creando una clase anónima:
SwingWorker worker = new SwingWorker<MyXMLFile, Void>() {
public MyXMLFile doInBackground() {
MyXMLFile myXMLDoc = loadXML();
return myXMLDoc;
}
};
Para que la tarea se ejecute debemos invocar al método execute
de SwingWorker
.
Dicho método se encarga de llamar a doInBackground
asegurándose de que se
ejecuta en un hilo diferente al EDT.
Pero, ¿cómo recupero el resultado de la larga tarea?. SwingWorker
proporciona el método
get
para recuperar el resultado, pero sólo debe ser llamado una vez la tarea ha
finalizado. Existen dos métodos para asegurarnos que la tarea ha finalizado:
- Sobrescribiendo el método
done
, el cual es ejecutado en el hilo manejador de eventos después de que la tarea haya finalizado.
private MyXMLFile doc;
//...
SwingWorker<Document, Void> worker = new SwingWorker<MyXMLFile, Void>() {
public MyXMLFile doInBackground() {
MyXMLFile myXMLDoc = loadXML();
return myXMLDoc;
}
public void done() {
doc = get();
}
};
- Registrar un listener mediante el método
addPropertyChangeListener(PropertyChangeListener listener)
el cual será notificado de cambios en el estado deSwingWorker
Actualización (26-01-2010): Uso avanzado de SwingWorker