概要

JDK 6で新しくなったSwingWorkerを使って、処理の中断や進捗状況の表示更新などを行います。

サンプルコード

class Task extends SwingWorker<String, String> {
  @Override public String doInBackground() {
    System.out.println("doInBackground() is EDT?: " + EventQueue.isDispatchThread());
    try {
      Thread.sleep(1000);
    } catch (InterruptedException ie) {
      return "Interrupted";
    }
    int current = 0;
    int lengthOfTask = 120; // list.size();
    publish("Length Of Task: " + lengthOfTask);
    publish("\n------------------------------\n");

    while (current < lengthOfTask && !isCancelled()) {
      try {
        Thread.sleep(50); // doSomething(file = list(current));
      } catch (InterruptedException ie) {
        return "Interrupted";
      }
      setProgress(100 * current / lengthOfTask);
      publish(".");
      current++;
    }
    publish("\n");
    return "Done";
  }
}

class RunAction extends AbstractAction {
  public RunAction() {
    super("run");
  }

  @Override public void actionPerformed(ActionEvent evt) {
    System.out.println("actionPerformed() is EDT?: " + EventQueue.isDispatchThread());
    final JProgressBar bar = new JProgressBar();
    runButton.setEnabled(false);
    canButton.setEnabled(true);
    anil.startAnimation();
    statusPanel.removeAll();
    statusPanel.add(bar);
    statusPanel.revalidate();
    bar.setIndeterminate(true);

    worker = new Task() {
      @Override protected void process(List<String> chunks) {
        System.out.println("process() is EDT?: " + EventQueue.isDispatchThread());
        if (!isDisplayable()) {
          System.out.println("process: DISPOSE_ON_CLOSE");
          cancel(true);
          return;
        }
        for (String message : chunks) {
          appendText(message);
        }
      }

      @Override public void done() {
        System.out.println("done() is EDT?: " + EventQueue.isDispatchThread());
        if (!isDisplayable()) {
          System.out.println("done: DISPOSE_ON_CLOSE");
          cancel(true);
          return;
        }
        anil.stopAnimation();
        runButton.setEnabled(true);
        canButton.setEnabled(false);
        statusPanel.remove(bar);
        statusPanel.revalidate();
        String text = null;
        if (isCancelled()) {
          text = "Cancelled";
        } else {
          try {
            text = get();
          } catch (InterruptedException | ExecutionException ex) {
            ex.printStackTrace();
            text = "Exception";
          }
        }
        appendText(text);
      }
    };
    worker.addPropertyChangeListener(new ProgressListener(bar));
    worker.execute();
  }
}

class CancelAction extends AbstractAction {
  public CancelAction() {
    super("cancel");
  }

  @Override public void actionPerformed(ActionEvent evt) {
    if (worker != null && !worker.isDone()) {
      worker.cancel(true);
    }
    worker = null;
  }
}
View in GitHub: Java, Kotlin

解説

  • JDK 6以前のSwingWorker.javaから一部メソッド名が変更されているが、基本的な使用方法は同じ
  • SwingWorker#execute()メソッドで処理が開始され、SwingWorker#doInBackground()メソッドがバックグラウンドのワーカースレッドで実行される
  • EDTで実行する必要のある処理(上記の例では処理中にJTextAreaへのメッセージの書き出し)はSwingWorker#process()メソッドをオーバーライドしてSwingWorker#publish()メソッドで呼び出したり、SwingWorker#firePropertyChange()を使用する
  • プログレスバーの処理にはSwingWorker#setProgress(int)が予め用意されているので、SwingWorker#addPropertyChangeListener(ProgressListener)を設定するだけで使用可能
  • SwingWorker#setProgress(int)で設定できるのは0から100で固定
    protected final void setProgress(int progress) {
      if (progress < 0 || progress > 100) {
        throw new IllegalArgumentException("the value should be from 0 to 100");
      }
      // ...
    
  • 実行中の処理のキャンセルはSwingWorker#cancel(boolean)メソッドで実行する
    • キャンセルされたかどうかはSwingWorker#isCancelled()メソッドで判定可能

  • 現在のスレッドがイベントディスパッチスレッド(以下EDT)かどうかを調べるEventQueue.isDispatchThread()をこのサンプルで使用してテスト
  1. actionPerformed() is EDT?: true
    • 現在のスレッド(このサンプルではEDT)でボタンを選択不可にしたり、SwingWorker#execute()を実行している
  2. doInBackground() is EDT?: false
    • ワーカースレッド(バックグラウンド)で重い処理を行い、EDTをブロックして停止状態にならないようにする
  3. process() is EDT?: true
  4. done() is EDT?: true
    • Swing関連のすべての作業(例えばJProgressBarの進捗表示更新)はEDTで行う必要があるので、process()done()メソッド内で実行する

  • SwingWorker#process()メソッド内などでJPanel#isDisplayable()を呼び、アプリケーション(frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);が設定されている)が終了している場合は、タスクを中断することでSwingWorkerが生き残るのを防止

参考リンク

コメント