Swingとスレッドと再描画

.NET WindowFormsのプログラマがSwingのアプリケーションを書く際に嵌る原因はいろいろあると思うが、その中でも一番は表題にもあるようにちょっとしたことで画面の再描画が止まってしまうことではないだろうか。
Swingの場合、GUIの基本的な処理が実行されているスレッドは通称「Event Dispatch Thread : 以降EDTと略する」と呼ばれており、ここでの処理を遅延させることは直ちに画面の再描画を滞らせることに繋がることを常に意識する必要がある。

と書いているのは自らが何度となくこの罠に嵌っているからだ。最近では自作のGlassPane(ガラス区画)を用意したはいいが、再描画処理(paintComponentメソッド)が全く呼ばれないことで数日嵌った。(答えは簡単で、EDTじゃないつもりがやっぱりEDTで時間を費やしていたのが原因だった。)

.NETもJava(Swing)もGUIとスレッド処理の決まり事は同じだ。

  • GUIスレッド(EDT)では時間のかかるタスクを実行してはならない。
  • GUIコンポーネントには、GUIスレッド(EDT)上でのみアクセスしなくてはならない。

しかし、.NETの場合にはメインスレッドで処理をする場合はルーズでもそれほど問題にはならないことが多い。(それでもメッセージループを滞らせる程の処理はまずいが)ところがSwingの場合、EDTで(イベントリスナによる通知処理が動作するスレッドはEDTだ)少しでも処理が滞留する場合はすぐに再描画が止まるので、SwingWorkerクラス等を使って他のスレッドに処理を任せることが必要になる。

SwingWorkerクラスはGenericsに対応しており、javadocを見ると使い方が面倒に感じるが、単に別スレッドで処理を実行するだけなのであれば簡単なイディオムで実行できる。

SwingWorker worker = new SwingWorker(){
    @Override
    protected Object doInBackground() throws Exception {
        //時間のかかる処理又はメソッド//
        return null;
    }}; 
worker.execute();

これだけでdoInBackgroundメソッド内に書かれた処理はEDTとは別スレッドで動作することが保証される。