SwingWorkerを使ってみる
SwingWorkerを使って時間のかかる処理の状況をプログレスバーで表示してみました。主な課題は、この手の処理のコールドスポット(⇔ホットスポット。共通部品になりそうな部位)はどこか自分なりに感じ取ることです。
- JDK6u24、NetBeans7.0β2
ネットを検索するとSwingWorkerに関するサンプルはたくさん見つけられるのですが、これらのコードは、ほとんどのケースで時間のかかる処理をGUI(Swing部品を取り扱っている部位)に直接埋め込んでいます。しかし、時間のかかる処理というのは本来GUIとは無関係であることが多いと思いますので、時間のかかる処理を別立てにすることを目標とします。
今回は、時間のかかる処理をEratosthenes#calculate()に作ってあります(MAX_NUMBER迄の素数を求める)。SwingWorker#doInBackground()内で時間のかかる処理を実行します。このメソッドはイベントディスパッチスレッド (EDT) ではありませんので、このメソッドで実行します。
EratosthenesオブジェクトはGUIやSwingWorkerに依存しません。共通部品のAbstractProgressListener(末尾”所感”参照)を通じてGUI側のオブジェクトに処理を委譲します。AbstractProgressListener派生の無名派生クラス#actionPerformed()がそれです。
actionPerformed()の中の処理は、他のサンプルにも見られる処理と同様で、Eratosthenes#calculate()にて実行中の処理状況をSwingWorker#publish(getValue())しています。それを受けて、SwingWorker#process()でプログレスバーを更新しています。これ以降は、通常のSwingWorkerの使い方同様だと思います。
public class AppPanel extends javax.swing.JPanel { private int MAX_NUMBER = 1000 * 1000; // 対象数(最大側) private SwingWorker<Void, Integer> worker = null; private void startButtonActionPerformed(java.awt.event.ActionEvent evt) { worker = new SwingWorker<Void, Integer>() { @Override public Void doInBackground() throws Exception { // Eratosthenes.calculate()は時間のかかる処理でGUIとは無関係 Eratosthenes.calculate(new AbstractProgressListener() { @Override public boolean actionPerformed() { boolean canceled = isCancelled(); if (!canceled) { publish(getValue()); } return !canceled; } }, MAX_NUMBER); return null; } @Override public void process(java.util.List<Integer> chunks) { setProgress(chunks.get(chunks.size() - 1)); } }; worker.addPropertyChangeListener(new ProgressListener(progressBar)); worker.execute(); } private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) { if (worker != null && !worker.isDone()) { worker.cancel(true); } worker = null; } }
ちなみに、時間がかかる処理の本体であるEratosthenes#calculate()は、以下のようになっています。Swingには全く依存していません。
public class Eratosthenes { public static void calculate(AbstractProgressListener listener, int count) { : do { : // GUIに状況を表示できるようパーセンテージをセットする listener.putValue(処理状況を表すパーセンテージ); // GUI側に制御のトリガを与える。 // キャンセルならば終了するためbreak if (!listener.actionPerformed()) break; } while (まだ素数判断する数値が残っている); : } }
これらの関係をクラス図にしてみます。
蛇足ですが、AbstractProgressListenerクラスはActionクラスで良いのかもしれません。今回は、私がうまくActionクラスを使いこなせなかったため、AbstractProgressListenerクラスを作成しましたが、既存のクラスで十分ならば既存のクラスを使うべきだと思います。
所感
SwingWorker#doInBackground()の中で、時間のかかる処理を(が)委譲して(されて)います。その際にGUI側であるSwingWorkerと非GUI側であるEratosthenesの間を取り持つ橋渡し役は、Swingに依存しないクラスを使用すると、時間のかかる処理をSwingから切り離すことができました。
こうすることで、時間のかかる処理をCUI等Swingに依存しない部位にて再利用することができると思います。
また、SwingWorkerを包含するAppPanel側は、時間のかかる処理(ホットスポット)をGUIから引き離すことができました。今回は手が届きませんでしたが、時間がかかる処理の時に表示するプログレス・ダイアログをジェネリックなクラスでひとつ作っておけば、こちらも共通部品として再利用可能と思われます。