Evitando bloquear el GUI con SwingWorker

En ocasiones nuestros programas necesitan realizar operaciones de larga duración que llevan mucho tiempo como consultas a bases de datos o accesos de lectura/escritura a archivos grandes. Esto ocasiona que la GUI de nuestra aplicación no se refresque y se quede ‘congelada’ hasta que termina la operación de larga duración, lo que confunde al usuario que no sabe si debe esperar o si la aplicación se ha quedado ‘colgada’. Cualquier tarea que lleve más de medio segundo debería ser ejecutada en un hilo aparte del hilo de ejecución de swing.

Vamos a ver una pequeña introducción de la forma de gestionar los hilos de swing para hacernos una idea general de su funcionamiento.

Básicamente podemos decir que hay dos hilos de ejecución principales: el hilo que inicia la aplicación cuando creamos la GUI y el EDT conocido como Event Dispatching Thread que es el que nos interesa.
El EDT es el hilo de ejecución de las aplicaciones Swing. Este se encarga de dibujar y actualizar los componentes del GUI además de registrar los eventos generados por estos. Es una cola de tipo FIFO que procesa las tareas de una en una secuencialmente, lo que significa que hasta que una tarea no termina no puede empezar la siguiente.
Aquí es donde se encuentra el problema, ya que si ejecutamos las operaciones de larga duración dentro del hilo EDT provocamos que no se puedan repintar los componentes Swing asi como impedimos que pueda responder a eventos de teclado o ratón.


SwingWorker

Aquí es donde entra SwingWorker que es una clase desarrollada por Sun que nos permite ejecutar procesos en un hilo independientemente del EDT. SwingWorker no venía con la distribución de java, al menos hasta la versión 5, por lo que debe ser descargada de:
Página del proyecto SwingWorker

Como métodos más importantes se la clase SwingWorker podemos citar los siguientes:

  • public Object construct(){}: en este método debemos ejecutar la tarea de larga duración.
  • public void finished(){}: este método se ejecuta automáticamente cuando termina la ejecución de la tarea definida en construct(). Aquí podemos definir la respuesta a la finalización de la tarea.
  • public Object get(){}: devuelve el valor creado por el método construct().

En este ejemplo se muestra un JFrame que contiene un boton que, al ser pulsado, simula una tarea de 5 segundos de duración. Mientras la tarea se ejecuta, se muestra un diálogo que contiene una barra de progreso indeterminada que indica al usuario que debe esperar mientras la tarea se finaliza. La tarea es llamada en el método construct de SwingWorker.
Una vez finalizada la tarea, mediante el método finished, se oculta el diálogo ya que ha terminado el proceso. Si no se hubiese utilizado SwingWorker, tanto el botón como el diálogo se hubieran quedado congelados hasta terminar la ejecución de la tarea pesada. Esto se puede probar ejecutando tarea.comienza() fuera del swingworker.
Hay que recordar que SwingWorker no puede ser reutilizado por lo que hay que crear una instancia nueva cada vez que se vaya a utilizar.


import java.awt.*;
import javax.swing.*;

public class SwingWorkerDemo {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        createAndShowGUI();
      }
    });
  }
  
  private static void createAndShowGUI(){
    Ventana ventana = new Ventana();
    ventana.setVisible(true);
    ventana.pack();
    ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }
}

class Ventana extends JFrame{
  JButton botonStart = new JButton("Start");
  Dialogo dialogo;
  TareaLarga tarea = new TareaLarga(this);
  
  public Ventana(){
    super("SwingWorker");
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(botonStart);
    
    botonStart.addMouseListener(new MouseListener(){
      public void mouseClicked(MouseEvent arg0) {}
      public void mousePressed(MouseEvent arg0) {}
      public void mouseReleased(MouseEvent arg0) {
        dialogo = new Dialogo();
        dialogo.setVisible(true);
        dialogo.pack();
        
        final SwingWorker worker = new SwingWorker() {
          public Object construct() {
            tarea.comienza();
            return null;
          }
          //una vez terminada la tarea, se
          //elimina el dialogo
          public void finished(){
            dialogo.dispose();
          }
        };
        worker.start();
      }
      public void mouseEntered(MouseEvent arg0) {}
      public void mouseExited(MouseEvent arg0) {}      
    });  
  }
}

class Dialogo extends JDialog{
  //un dialogo con barra de progreso
  JProgressBar progressBar;
  public Dialogo(){
    setTitle("Espere por favor");
    setLayout(new FlowLayout());
    progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    
    add(new JLabel("Analizando: "));
    add(progressBar);
  }
}

class TareaLarga{
  JFrame frame;
  public TareaLarga(JFrame frame){
    this.frame = frame;
  }
  public void comienza(){
    try {
      //simulamos una tarea larga
      Thread.sleep(5000);
      JOptionPane.showMessageDialog(frame, "Finalizado");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
Sin categoría

Deja una respuesta