Тема: SwingWorker (виконання довгих розрахунків в SWINGу)
Під час створення графічного інтерфейса, часто виникає питання, а що ж робити, коли при натисканні на якусь кнопку виконується "важка" (довга) процедура (підключення до бази даних, читання даних з великого файла, виконання довгих розрахунків, тощо). Пам'ятаю, як дивувався, чому ж не поновлюється JProgressBar, при підключенні до бази даних. Весь інтерфейс, так би мовити "завмирає" на певний час і навіть кнопка, яку ми натиснули, так і відображається натиснутою. І ось тут, ми стоїмо на роздоріжжі, потрібно застосовувати багатопоточність.
Є два варіанти:
1. Реалізувати даний механізм власноруч (напевно, найкращий варіант, проте, значно важчий, аніж 2 варіант).
2. Використати абстрактний клас SwingWorker (public abstract class SwingWorker<T,V> extends Object implements RunnableFuture<T>).
Що ж то за Worker, такий?
Даний, клас з'явився в 6 версії Java, тобто, ще раз підкреслюю, що розробники, раніше і без нього справлялися.
Проте він був створений, для полегшення вирішення даних задач. Тобто, давайте поглянем на мінімум, який потрібно зробити, для виконання задачі в іншому потоці:
1. Написати клас, який наслідується від класа SwingWorker.
2. Не забуваєм, що SwingWorker абстрактний клас, тому потрібно реалізувати абстрактний метод doInBackground() (protected abstract T doInBackground() throws Exception).
3. Створити об'єкт нашого класу та викликати метод execute() (public final void execute()).
Тобто, як бачимо, нічого надзвичайного не має. А тепер, давайте більш детально пройдемось, по даному класу.
SwingWorker<T,V>
Що таке T та V?
T - тип результату, що повертається методом doInBackground() (можна сказати кінцевий результат).
V - тип проміжкових результатів (відправляється методом publish(V... chunks) (protected final void publish(V... chunks)) з методу doInBackground(), ці результати потрапляють (передаються) до методу process(List<V> chunks) (protected void process(List<V> chunks))).
Самий, цікавий, для нас метод - doInBackground(). Ось тут і потрібно виконувати, нашу "важку" роботу (фонову задачу). Оскільки даний метод буде запущено в окремому потоці. Також треба наголосити, на те, що з елементами GUI працювати в даному методі НЕ РЕКОМЕНДУЄТЬСЯ, проте є деякі виключення. Методи - thread safe (потокозахищені) - JTextComponent.setText(), JTextComponent.print(), JTextArea.insert(), JTextArea.append(), JTextPane.insertIcon(),... (див. офіційну документацію).
Не обов'язково, але при бажанні, з даного метода можемо передавати проміжкові результати методом publish(V... chunks).
Перевизначення методу process(List<V> chunks) використовується, для обробки (показу) проміжних результатів. Ось в цьому методі вже можна працювати, з елементами GUI, оскільки даний метод викликається з Event Dispatch Thread ("рідного" для SWINGа - потоку надсилання подій).
Метод done() (protected void done()) викликається після завершення виконання методу doInBackground(). Тобто, "довга" робота скінчена, ми знову повертаємося в Event Dispatch Thread. І якщо, ми щось хочемо змінити в інтерфейсі, після "довгої" роботи слід перевизначити метод done().
Ну і звичайно, не слід забувати про метод execute(). Який і запускає виконнаня в окремому робочому потоці.
Також, потрібно підкреслити, що об'єкт даного класу, скажімо так, для одноразового використання. Ще один нюанс, даного класу: максимальна кількість потоків - 10. Це означає, що за допомогою класу SwingWorker паралельно можуть виконуватися тільки 10 фонових задач. Якщо потоків створено більше, SwingWorker, ставить нові потоки в чергу очікування.
Тобто, нічого, складного в класі SwingWorker немає. А головне, що даний клас сам потурбується про переключення потоків.
Ось простенький, код, де можна побачити, як застосовувати SwingWorker:
import java.util.List;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Example extends JFrame {
private JLabel label = new JLabel();
public Example() {
setTitle("Тест SwingWorker");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button = new JButton("Виконання");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new MyWork().execute();
}
});
setLayout(new FlowLayout());
add(button);
add(label);
setSize(300, 300);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Example();
}
});
}
class MyWork extends SwingWorker<Void, String> {
// даний метод запускається в окремому потоці, не в Event Dispatch
// Thread, тут виконується фонова задача
@Override
protected Void doInBackground() throws Exception {
int i = 0;
// надсилання проміжкових результатів
publish("Підраховуємо i");
while (i < Integer.MAX_VALUE) {
i++;
}
// надсилання проміжкових результатів
publish("i=" + i);
Thread.sleep(1000);
// надсилання проміжкових результатів
publish("Заснули");
Thread.sleep(5000);
// надсилання проміжкових результатів
publish("Прокинулися");
Thread.sleep(1000);
return null;
}
// ось цей метод запускається в Event Dispatch Thread, для обробки
// проміжкових результатів
@Override
protected void process(List<String> chunks) {
for (String x : chunks) {
label.setText(x);
}
}
// даний метод запускається в Event Dispatch Thread, після виконання
// методу doInBackground()
@Override
protected void done() {
label.setText("");
}
}
}
P.S. Ну і звичайно, давайте, не забувати користуватися офіційною документацією Class SwingWorker<T,V>
P.P.S. Звичайно, дана стаття призначена, для початківців, якщо в когось є якісь доповнення, буду радий їх почитати.