いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Swingスレッド処理-004(SwingWorkerについて)


前回までの記事


前回、SwingWorkerというものがあることについて触れました。
このクラスは、現状のJ2SEには含まれていません。(個別にダウンロードする必要があります。)
ですが、リクエストが多かったのかどうか知りませんが次期バージョンには含まれることになっています。


元々のSwingWorkerはここにあります。


SDNのドキュメント Using a Swing Worker Thread(リンクは、Swingスレッド処理の第一回の記事を参照願います)
もこのクラスを元に書かれていますが、このクラスはさらにパワーアップしていまして、次期バージョンに取り込まれる
ものは以下のものとなります。


上記のサイトでダウンロードできるものが次期バージョンに取り込まれています。つまり、ここでダウンロードできるものは
mustangより前のバージョンで使用するためのバックポートです。


この記事では、javadesktopのSwingWorkerを取り上げます。
このクラスのjavadocは以下です。

丁寧にjavadocを書いてくれているので、見たらわかるかもしれませんが、このクラスを
使用すると

  • 別スレッドにて処理を行う部分
  • EDTにて処理を行う部分

を綺麗に分けられます。また、前回の記事のように自分でスレッドを作成する必要がなくなります。


典型的なパターンは以下のような感じです。

import org.jdesktop.swingworker.*;

//
// SwingWorkerクラスはジェネリック対応となっています。
//
// 型を定義する際は、2つの型パラメータが必要となっており
// 一つ目がdoInBackgroundメソッドからの戻り値の型で、もうひとつが
// publish,processメソッドにてやり取りする際の型となります。
//
class MyTimeConsumingTaskWorker extends SwingWorker<String, String>{
    
    /**
     * このメソッドにて、別処理で走らせる時間のかかる処理を記述.
     *
     */
     public String doInBackground(){
         if(!isCancelled()){
             //
             // 時間のかかる処理を記述.......
             //

             // 途中経過をユーザに知らせたりする場合はpublishする
             publish("もうすぐ処理終了ですよ〜〜〜〜");

             //
             // 処理の終了時になんらかの値を返す
             //
             return "処理終了しました。";
         }else{
             return "キャンセルされました。";
         }
     }

    /**
     * このメソッドは、doInBackgroundメソッドにて、publishメソッドがコールされた際に
     * 呼ばれます。引数の値はpublishメソッドで指定された値のリストとなります。
     * (つまり、publishメソッドには可変長の引数が渡せます。詳しくはjavadocを参照願います)
     *
     */
     public void process(java.util.List<String> chunks){
          jLabel.setText(chunks.get(0);
     }

    /**
     * このメソッドは、doInBackgroundメソッドの処理が終了した後でコールされます。
     * doInBackgroundメソッドの戻り値を取得したい場合は、getメソッドを使用します。
     * (注意:getメソッドを呼ぶと、処理終了までブロックされますので注意してください)
     *
     */
     public void done(){
         try{
             jLabel.setText(get());
         }catch(Exception ex){
             // サンプルなので例外処理はスキップ
             ex.printStackTrace();
         }
     }
}

他にも、このクラスにはPropertyChangeListenerを設定できたり、進行状態を設定できる
バウンドプロパティをもっていたりとなにかと便利です。


実際に実行する場合は、SwingWorkerのインスタンスを作成して、
executeメソッドをコールします。このメソッドはすぐに制御を返してきますので
中の処理は当たり前ですが非同期となります。


以下、簡単なサンプルです。画面には、処理用のボタンが左右に2つあり、
左のボタンがSwingWorkerを使用せず時間のかかる処理もまとめてEDTで処理している例で、
右のボタンがSwingWorkerを使用して時間のかかる処理を行っています。
真ん中のボタンは、確認用のボタンです。処理がブロックされている間は押下できないことが
確認できます。

[サンプル]

import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.*;
import javax.swing.*;
import org.jdesktop.swingworker.*;

public class SwingWorkerTest extends JFrame{
    JButton btn1;
    JButton btn2;
    JButton btn3;
    JLabel  statusLabel;

    public static void main(String[] args) throws Exception{
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                new SwingWorkerTest().setVisible(true);
            }
        });
    }

    public SwingWorkerTest(){
        super("SwingWorker TEST");

        initComponents();
    }

    protected void initComponents(){
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(false);

        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        btn1        = new JButton(new BlockedAction());
        btn2        = new JButton(new NonBlockedAction());
        btn3        = new JButton("処理中に、このボタンが押下することができたらブロックされてない");
        statusLabel = new JLabel("ここにステータスが表示されます....");

        contentPane.add(btn1,        BorderLayout.WEST);
        contentPane.add(btn2,        BorderLayout.EAST);
        contentPane.add(btn3,        BorderLayout.CENTER);
        contentPane.add(statusLabel, BorderLayout.SOUTH);

        pack();
    }

    class BlockedAction extends AbstractAction{
        public BlockedAction(){
            super("ブロックされる場合");
        }

        public void actionPerformed(ActionEvent evt){
            try{
                statusLabel.setText("処理を実行中です・・・");

                System.out.println("sleep start....");

                TimeUnit.SECONDS.sleep(7);

                statusLabel.setText("もうすぐ処理がおわります・・・");

                TimeUnit.SECONDS.sleep(3);

                System.out.println("sleep end  ....");

                statusLabel.setText("処理が終了しました。");
            }catch(InterruptedException ex){
                ex.printStackTrace();
            }
        }
    }

    class NonBlockedAction extends AbstractAction{
        public NonBlockedAction(){
            super("ブロックされない場合");
        }

        public void actionPerformed(ActionEvent evt){
            statusLabel.setText("処理を実行中です・・・");

            SwingWorker worker = new SwingWorker<String, String>(){
                public String doInBackground(){
                    try{

                        System.out.println("sleep start....");

                        TimeUnit.SECONDS.sleep(7);

                        publish("もうすぐ処理がおわります・・・");

                        TimeUnit.SECONDS.sleep(3);

                        System.out.println("sleep end  ....");

                    }catch(InterruptedException ex){
                        ex.printStackTrace();
                    }

                    return "処理が終了しました。";
                }

                public void process(java.util.List<String> chunks){
                    statusLabel.setText(chunks.get(0));
                }

                public void done(){
                    try{
                        statusLabel.setText(get());
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            };

            worker.execute();
        }
    }

}


================================
過去の記事については、以下のページからご参照下さい。