GUI と Thread

だれが GUI event を処理?

GUI アプリを作成していると、一般の逐次プログラムを書いているのと全然違いますよね。

  • 一般の逐次プログラム:main 関数から始まって、別の関数呼んで、、、、
  • GUI: 「テンプレートに沿って色々部品配置して、ハンドラ書いて」で、システムがなんとかしてくれる

ってことで、今回はGUI の処理は誰が処理しているかと、インタラクティブなアプリケーションを作成する上でのスレッドに関する注意点などを解説します。

その前に

スレッドという用語をこちらの「スレッドとは」で確認しておきましょう。

GUIスレッド

まず、Java Swing における典型的な GUI プログラムの基本的な形を示します。

guiOverview.png

皆さんのプログラムは、基本

  • main thread は、GUI 部品(JFrame)を配置して終了

していることが多いと思います。

じゃ、GUI の処理(描画処理、イベント処理)は誰がやっているんだってことですが、実は、GUI 用のスレッドが main thread とは別に動いて、処理を行っています。

  • active な GUI 部品がある限り、プログラムは処理をつづける。
  • GUI の event dispatching thread が、イベントハンドラを順次呼び出して処理を進める

という形になっています。

ちなみに、android の場合も、前面で起動しているプロセスにはメインスレッド=UI threadが一つ走っており、GUI コンポーネントへの操作を一元管理します。ユーザが操作可能な画面は Activity と呼ばれており、一つのアプリは、複数の Activity から構成されることが多いです。一方で、プロセスの中には、Activity を持たないサービスプロセスというものも存在し、音楽再生やファイルのダウンロードなど、バックグラウンド処理に利用可能です。

ところで、イベントハンドラ内で長時間かかる処理をすると、その間、 GUIが反応しなくなります。例えば、

  • 1分間スリープする
  • ユーザ入力待ちをする
  • ネットワーク入力待ちをする

といった処理をハンドラ内で行うのは不適当です。 こういう場合は、該当処理を別スレッドに分けで実行し、ハンドラ自身は、すぐに終了させるべきです。

あと、1秒に1回画面を再描画させたいんだけど、なんて処理も GUI スレッド以外からトリガーを書けることになります。

GUI 部への実行依頼

一方で、** GUI の処理は、基本的には GUI スレッドのみで行うこと** となっています。 具体的には、Swing コンポーネントに対する多くの操作は、Thread Safeではなく、別スレッドから操作された場合、挙動が変になるかもしれません。Android でも同様です。

ただ、簡単な GUI アプリでは、あまり気にしなくても問題は起きません。というのも、main thread はGUI コンポーネントを作成して終了し、他のスレッドも使わないってことが多いでしょうから。 javax.swing.SwingUtilities のメソッドを用いて、GUI スレッドに仕事を依頼します。

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        /* GUI 生成・アクセス操作 */
    }
});

simpleGui.png

invokeLater(Runnable)は、GUI thread に対して「そのうちやっておいて〜」と依頼して、あとは GUI thread に任せます。 依頼が完了するのを待ちたかったら、invokeAndWait(Runnable)を使います。 こちらは、run() が終了するのを待つので、java.lang.InterruptedExceptionの可能性があります。

SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        /* GUI 生成・アクセス操作 */
    }
});

GUI のセットアップ以外にも、他のスレッドから GUI に処理を依頼することもあるでしょう。 例えば別のスレッドがネットワークからの入力待ちをし、入力があれば随時 GUI に表示するとか。 以下の例では、main() 関数がそのままネットワーク待ちをして、入力に応じてメッセージ表示を依頼しています。

netGUI.png

static void main(String[] args) {
   SwingUtilities.invokeAndWait(new Runnable() {
      public void run() { /* GUI Thread に実行依頼 */
            GUIsetup();
      }
  });
  while(true) {
      Object msg = comm.recv();
      SwingUtilities.invokeLater(new Runnable() {
          public void run() { /* GUI Thread にメッセージ処理依頼 */
             showMessage(msg);
          }
      }
  }
}

Android の場合: UI ツールキットは Thread Safe ではなく、UI コンポーネントへの操作は、UI スレッドで一元管理することになっています。このため、他のスレッドから描画処理をおこなう場合は、UI スレッドに対して以下のメソッドを通じて処理を依頼します。詳しい話は、developer.android.comの解説を見てください。