Antes de começar é importante entender que este conceito está fortemente entrelaçado ao conceito de “Event Dispatch Thread”, ou seja, se você não entende o básico sobre o uso do EDT (Event Dispatch Thread), provavelmente não entenderá o porque usar o SwingWorker invés de uma Thread Simples criada através de “Runnable”.

O Event Dispatch Thread (comumente conhecido pelo acrônimo EDT) é nada mais que uma “grande” Thread que encapsula todos os eventos do Swing, ou seja, quando você cria um ambiente gráfico em Java (seja um Jframe, JDialog ou qualquer que seja) este GUI está rodando na Thread EDT. Quantas vezes você não recebeu uma exception retornada de “AWT-eventqueue-0” ? Isso significa que sua exception está sendo lançada através do EDT.

Quando clicamos em um botão por exemplo, a ação deste é acionada no EDT e por alguns segundos sua tela fica travada até o processamento terminar. Por esse motivo é altamente recomendável que apenas processamentos curtos sejam acionados diretamente no EDT, para casos em que você saiba que o processamento irá demorar, é necessário a utilização de uma outra Thread (em paralelo a EDT) e é exatamente neste ponto que surge o SwingWorker.

O nome SwingWorker vem de um conceito mencionado no próprio manual da Oracle que se refere a tarefas que demandam um longo período de tempo para terminarem, que devem ser executadas em um “worker thread”, ou seja, são threads que literalmente farão o trabalho pesado que o EDT não deve fazer.

Imagine a seguinte situação: Você deseja realizar uma persistência de 10mil registros no banco de dados, todos de uma vez só, esse processamento pode demorar 5 minutos ou 1hora dependendo da estrutura em que você está trabalhando (considere estrutura sendo: rede, banco de dados, conexão, distância e etc.). Quando o usuário clicar em “Salvar” ele espera que em pelo menos 3 segundos a 1 minuto receba uma resposta (sucesso, erro ou aguarde), se você o deixa esperando 1 hora com a tela travada (congelada), possivelmente ele já terá desligado o computador pensando que o sistema travou, mas na verdade ele ainda está processando os 10mil registros, certo?

O ideal na situação descrita acima é mostrar pelo menos um “Aguardando processando...” para o usuário saber que o sistema não travou. Além disso, podem surgir outros problemas como por exemplo: o sistema pode ter travado nos primeiros 5 minutos e o usuário vai aguardar 1 hora por que você disse que ele deveria aguardar 1 hora para que tudo seja salvo, uma situação chata e constrangedora.

SwingWorker

Com o SwingWorker criamos uma Thread paralela a EDT e trabalhamos com a quantidade de processamento que desejarmos. Mas isso não basta, com o SwingWorker ainda podemos ficar atualizando os componentes do nosso formulário, ou seja, da EDT. Então a situação aqui fica bem simples de compreender:

  • Você tem uma Thread principal chamada de EDT (Event Dispatch Thread) que é responsável por controlar todos os evento que ocorrem em uma janela X.
  • Quando o usuário clica no botão 'A' é acionado uma ação que fará determinado processamento, mas em vez de realizar todo processamento diretamente, você irá criar uma Thread SwingWorker (worker thread) e repassar a responsabilidade para ela.

Então vamos ver na prática como funciona. Vamos começar criando uma janela com uma Thread Simples, sem SwingWorker.

Listagem 1: Thread Simples (Sem SwingWorker)


import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class MainFrame extends JFrame {

 private JLabel countLabel1 = new JLabel('0');
 private JLabel statusLabel = new JLabel('Task not completed.');
 private JButton startButton = new JButton('Start');

 public MainFrame(String title) {
  super(title);

  setLayout(new GridBagLayout());

  countLabel1.setFont(new Font('serif', Font.BOLD, 28));

  GridBagConstraints gc = new GridBagConstraints();

  gc.fill = GridBagConstraints.NONE;

  gc.gridx = 0;
  gc.gridy = 0;
  gc.weightx = 1;
  gc.weighty = 1;
  add(countLabel1, gc);

  gc.gridx = 0;
  gc.gridy = 1;
  gc.weightx = 1;
  gc.weighty = 1;
  add(statusLabel, gc);

  gc.gridx = 0;
  gc.gridy = 2;
  gc.weightx = 1;
  gc.weighty = 1;
  add(startButton, gc);

  startButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
    start();
   }
  });

  setSize(200, 400);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  setVisible(true);
 }

 private void start() {
 Thread worker = new Thread() {
  public void run() {

   // Simulate doing something useful.
   for(int i=0; i<=10; i++) {
    // Bad practice
    countLabel1.setText(Integer.toString(i));

    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {

    }
   }

   // Bad practice
   statusLabel.setText('Completed.');
  }
 };

 worker.start();
}


 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {

   @Override
   public void run() {
    new MainFrame('SwingWorker Demo');
   }
  });
 }
}

Após clicar no botão de “start()” você perceberá que tudo funcionará normalmente. Mas todo bom profissional Java sabe que esta não é uma boa prática: trabalhar com GUI usando Threads simples que não tem uma “ligação” direta com o EDT. O SwingWorker foi desenvolvido exatamente com o intuito de prover uma relação entre EDT e Threads externas sem tornar seu sistema inconsistente e inseguro, então a velha questão é: Porque reinventar a roda se ela já foi inventada ? Usemos então o SwingWorker ao trabalhar com GUI.

Antes de mostrar a solução utilizando o SwingWorker, é importante você conhecer uma solução alternativa, que funciona bem, mas torna o código muito ilegível e de difícil manutenção, diferente do uso com SwingWorker que veremos mais a diante.

Listagem 2: Uma solução alternativa ao SwingWorker


import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class MainFrame extends JFrame {

 private JLabel countLabel1 = new JLabel('0');
 private JLabel statusLabel = new JLabel('Task not completed.');
 private JButton startButton = new JButton('Start');

 public MainFrame(String title) {
  super(title);

  setLayout(new GridBagLayout());

  countLabel1.setFont(new Font('serif', Font.BOLD, 28));

  GridBagConstraints gc = new GridBagConstraints();

  gc.fill = GridBagConstraints.NONE;

  gc.gridx = 0;
  gc.gridy = 0;
  gc.weightx = 1;
  gc.weighty = 1;
  add(countLabel1, gc);

  gc.gridx = 0;
  gc.gridy = 1;
  gc.weightx = 1;
  gc.weighty = 1;
  add(statusLabel, gc);

  gc.gridx = 0;
  gc.gridy = 2;
  gc.weightx = 1;
  gc.weighty = 1;
  add(startButton, gc);

  startButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
    start();
   }
  });

  setSize(200, 400);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  setVisible(true);
 }

 private void start() {
  Thread worker = new Thread() {
   public void run() {

    // Simulate doing something useful.
    for(int i=0; i<=10; i++) {

     final int count = i;

     SwingUtilities.invokeLater(new Runnable() {
      public void run() {
       countLabel1.setText(Integer.toString(count));
      }
     });

     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {

     }
    }

    SwingUtilities.invokeLater(new Runnable() {
     public void run() {
      statusLabel.setText('Completed.');
     }
    });

   }
  };

  worker.start();
 }



 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {

   @Override
   public void run() {
    new MainFrame('SwingWorker Demo');
   }
  });
 }
}

Enfim, mostrados o exemplo da Listagem 1 (que não deve ser aplicado) e o da Listagem 2 (que pode ser aplicado mas torna o código ilegível e de difícil manutenção), veremos agora o exemplo mais apropriado ao ser trabalhar com GUI em Java.

Listagem 3: Usando SwingWorker


import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class MainFrame extends JFrame {

 private JLabel countLabel1 = new JLabel("0");
 private JLabel statusLabel = new JLabel("Task not completed.");
 private JButton startButton = new JButton("Start");

 public MainFrame(String title) {
  super(title);

  setLayout(new GridBagLayout());

  countLabel1.setFont(new Font("serif", Font.BOLD, 28));

  GridBagConstraints gc = new GridBagConstraints();

  gc.fill = GridBagConstraints.NONE;

  gc.gridx = 0;
  gc.gridy = 0;
  gc.weightx = 1;
  gc.weighty = 1;
  add(countLabel1, gc);

  gc.gridx = 0;
  gc.gridy = 1;
  gc.weightx = 1;
  gc.weighty = 1;
  add(statusLabel, gc);

  gc.gridx = 0;
  gc.gridy = 2;
  gc.weightx = 1;
  gc.weighty = 1;
  add(startButton, gc);

  startButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
    start();
   }
  });

  setSize(200, 400);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  setVisible(true);
 }

 private void start(){
 SwingWorker<Void>, <Void> worker = new SwingWorker<Void>, <Void>() {
   @Override
   protected Void doInBackground() throws Exception {
    // Simulate doing something useful.
    for (int i = 0; i <= 10; i++) {
     Thread.sleep(1000);
     System.out.println("Running " + i);
     countLabel1.setText(Integer.toString(i));
    }

    return null;
   }
  };

  worker.execute();
 }


 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {

   @Override
   public void run() {
    new MainFrame("SwingWorker Demo");
   }
  });
 }
}

Conclusão

O principal objetivo deste artigo foi dar uma visão introdutória da utilização do SwingWorker e sua importância. Com isto você terá o conhecimento necessário para estudar a fundo outras funcionalidades que esta poderosa ferramenta oferece.