いろいろ備忘録日記

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

Swingスレッド処理-003(時間のかかる処理の実行について)


前回までの記事


前回、イベントディスパッチスレッドにて時間のかかる処理を行うとGUIがブロックされる
件について記述しました。そのような処理を行う場合、Swingでは以下のようにします。

  1. 時間のかかる処理の部分を別スレッドにする
  2. 上記の処理が終了した後で、コンポーネントをイベントディスパッチスレッドから更新

(1)は特に問題ありません。処理を別のスレッドに移すだけです。問題となるのが
(2)です。時間のかかる処理を行った後で、コンポーネントを処理するわけですので
当然時間のかかる処理を行ったスレッドで行わざるをえないような形になってしまいます。
つまり、以下のような感じです。

jButton.setText("hehe");
jButton.addActionListener(new AnotherThreadProcessAction());

class AnotherThreadProcessAction extends AbstractAction{

    private Log log = LogFactory.getLog(AnotherThreadProcessAction.class);

    public void actionPerformed(ActionEvent evt){
        Thread t = new Thread(new Runnable(){
            public void run(){
                //
                // 時間のかかる処理を想定
                //
                try{
                    TimeUnit.SECONDS.sleep(10);
                }catch(InterruptedException ex){
                    log.error(ex.getMessage(), ex);
                }

                jButton.setText("Hoge");
            }
        });

        t.start();
    }
}

上記の場合、確かに別スレッドにて処理を行ってはいます(1)が、コンポーネントの更新を
イベントディスパッチスレッドにて行っていません。なのでこれは安全ではないです。
上記のコードの内、コンポーネントの更新部分をイベントディスパッチスレッドにて行う
には、以下のようにします。

jButton.setText("hehe");
jButton.addActionListener(new InvokeLaterAction());

class InvokeLaterAction extends AbstractAction{

    private Log log = LogFactory.getLog(InvokeLaterAction.class);

    public void actionPerformed(ActionEvent evt){
        Thread t = new Thread(new Runnable(){
            public void run(){
                //
                // 時間のかかる処理を想定
                //
                try{
                    TimeUnit.SECONDS.sleep(10);
                }catch(InterruptedException ex){
                    log.error(ex.getMessage(), ex);
                }

                //
                // コンポーネントの更新はEDTで行う
                //
                SwingUtilities.invokeLater(new Runnable(){
                    public void run(){
                        jButton.setText("Hoge");
                    }
                });
            }
        });

        t.start();
    }
}


これで時間のかかる処理が別スレッドにて行われ(1), コンポーネントの更新は
イベントディスパッチスレッドにて行われる(2)ようになりました。
使用しているメソッドは以下のものです。

SwingUtilities.invokeLater(Runnable doRun);

このメソッドは、引数で与えられたRunnableオブジェクトをイベントキュー(java.awt.EventQueue)
に置いて、すぐに制御を戻してきます。コンポーネントの更新を予約してくるイメージですね。
イベントディスパッチスレッドは、イベントキューの処理を順にこなしていきます。なので、予約が
一杯ある場合はすぐには反映されない場合もあったりします。このメソッドを呼んだ瞬間にコンポーネント
更新が行われるわけではないことに注意してください。


ちなみに、Swingの解説をしているサイト(書籍)では、よく

public static void main(String[] args){
    new XXXFrame().setVisible(true);
}

ってしているところを見かけますが、これも

public static void main(String[] args){
    SwingUtilities.invokeLater(new Runnable(){
        public void run(){
            new XXXFrame().setVisible(true);
        }
    });
}

ってしている方が無難です。何故ならsetVisibleする前にpack()されているかも
しれないし、また、後でsetVisible以降に処理を追加する事もあったりするからです。
確実にイベントディスパッチスレッド上で動くとわかっている場合以外で、コンポーネント
絡む部分はinvokeLaterを使用しているのが無難です。変なバグに遭遇せずにすみます。


ちなみに、invokeLaterと同じような感じのinvokeAndWaitというメソッドもSwingUtilitiesに
存在します。こちらは名前が示しているように、すぐに制御を返さずにそのRunnableオブジェクトが
実行されるまで待ちます。ダイアログの表示処理などによく使用したりします。こちらについては
割愛します。


ってことで、時間のかかる処理の仕方を書いたわけなのですが、なんか、Runnableの中にまたさらに
Runnableを作ったりしてややこしいですね。
時間のかかる処理を別スレッドにし、コンポーネントを処理後に更新するという事は、
よくあることですので、昔からSwingにはそれを行うためのユーティリティクラスが存在しています。
SwingWorkerといいます。ちなみに、J2SE 1.5(tiger)の時点ではこのクラスはjavaの配布物に含まれていません。
(J2SE 1.6(mustang))から、javax.swingに含まれることになっています)
次回は、SwingWorkerについての記事を書きます。


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