ユーザーインターフェイススレッド

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

ユーザーインターフェイススレッド (User interface thread) とは、グラフィカルユーザインタフェースでのメインスレッドのことを指す。UIスレッドと表記されることもある。本項では、GUIでのマルチスレッドに関するデザインパターンを記載する。ユーザーインターフェイススレッドは、JavaではイベントディスパッチスレッドAdobe Flashではprimordial workerと呼ばれる。

歴史的経緯[編集]

GUIアプリケーションにおいて、応答性を維持するためには、基本的にフレームの描画およびユーザー応答(さまざまなユーザー入力に対するアクション)はできるかぎり高いフレームレートで処理しないといけない[1]。例えば60fps (frames per second) の場合、1フレームの処理を1/60秒=約16ミリ秒以内に完了する必要がある。しかし、たとえどれほどプロセッサーの性能が向上したとしても、I/O処理や画像・動画のデコード、通信接続の確立など、完了までに長時間かかってしまう処理は必ず存在する。そういったものを含めて、すべての処理を1つのスレッドだけで実行すると、長時間かかる処理を実行している間はフレーム描画やユーザー応答の処理ができないため、UIの反応がなくなってしまう(ハングアップ、フリーズ)[2]。長時間かかる処理の途中に、フレーム描画やユーザー応答に関わるメッセージ処理をときどき挟みながら、長時間かかる処理を少しずつ進める、という方法もあるが、スマートでないうえに適用限界がある。応答性の低下を防ぐためにマルチスレッド化が必要だが、それをどのようにしてソフトウェアの構造設計に持ち込むか、ということに関して、歴史的には1980年代から色々と議論があった。

例えば、JavaAWT では、1996年の最初の時点では、単純にスレッド間でデータ共有型のマルチスレッドになっていた。しかし、データ共有するには、ロックをかけないといけないが、親コンポーネントから子コンポーネントを呼んだり、コールバックで子から親を呼んだり、アプリケーションからGUIライブラリを呼んだり、GUIライブラリからアプリケーションをコールバックしたりと、双方向に呼び出すことが多く、異なるスレッド間で双方向に呼び合うときは、ロックの順番に注意を払う必要がある。これはソフトウェアが非常に複雑になる原因となってしまう。また、ロック順序のミスが引き起こすデッドロックは常にではなくたまに発生したりすることの多いバグ(時間的確率要因が関与する偶発性のあるバグ)であり、バグ取りが大変になるという問題があった[3]

そこで、1997年JavaSwing からは、UI の操作は全てメインのUIスレッドであるイベントディスパッチスレッドから操作しなくてはならない、というルールを設けた。そして、2006年の Java 6 から、UI で重い処理をするために、ワーカーデザインパターンを採用した javax.swing.SwingWorker を搭載した。

現在[いつ?]では、多くのUIライブラリが、UIスレッドに操作を限定することと、ワーカーデザインパターンの組み合わせを採用している。

UIスレッドへの委譲[編集]

UIスレッドではないサブスレッドでの処理の進捗や完了を画面表示する場合などは、処理の途中でユーザーインターフェイス要素の操作が必要となる。そういった場面では、サブスレッドからUIスレッドに操作を依頼(委譲)することが、まず必要である。

Java[編集]

Java の場合は、以下の方法で、UI スレッドに委譲できる。J2SE 1.3以降ではどちらもEventQueueのメソッドが呼び出される実装となっている。

同期処理は、処理が完了するまで待つ。

他のライブラリ[編集]

ワーカーデザインパターン[編集]

Java[編集]

javax.swing.SwingWorker を継承して、実装できる。A→B→Cと処理が進むとき、Bが重い処理とすると、Bを SwingWorker.doInBackground() で、Cを SwingWorker.done() で処理する。

実装例:

SwingWorker<Document, Void> worker = new SwingWorker<Document, Void>() {
    public Document doInBackground() throws IOException {
        return loadXML(); // 重い処理
    }
    
    public void done() {
        try {
            Document doc = get();
            display(doc);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
};
worker.execute();

doInBackgound() の戻り値を done() の SwingWorker.get() で取り出せる。done() はUIスレッドで動作するが、doInBackgound() はUIスレッドとは別のスレッドで動作する。上記例では、done() の中で読み込んだ XML の表示を行う。

Groovy[編集]

上記の例を Groovy で書く場合は、groovy.swing.SwingBuilder に doLater(), doOutside(), edt() があり、下記のようによりシンプルに書ける。

doOutside {
    def doc = loadXML() // 重い処理
    edt { display(doc) }
}

他のライブラリ[編集]

同一インスタンスのワーカーを2回以上実行したときに、それは、逐次処理するべきなのか、並列処理するべきなのかという議論があり、UIライブラリによって様々であるが、Androidにおいては、2009年9月のAndroid 1.6までは逐次処理、2009年11月のAndroid 2.0からは並列処理、2011年2月のAndroid 3.0からはデフォルトは逐次処理と、色々と変遷した[4]。並列処理にすると使うときにバグを生みやすくなると言うのが、逐次処理に戻した理由である。なお、ワーカー自体が逐次処理であっても、複数のワーカーインスタンスを作り、別々に実行すれば並列に処理できる。

タイマー[編集]

UIスレッドで呼び出されるタイマーも多くのライブラリで備わっている。

参照[編集]

  1. ^ 必要となるフレームレートは、アプリケーションの用途やモニターのリフレッシュレートなどの環境によっても異なる。VRでは120fpsなど、さらに高いフレームレートが要求される。
  2. ^ Windows アプリケーションのハング状態の防止
  3. ^ Multithreaded toolkits: A failed dream?
  4. ^ AsyncTask - Android

外部リンク[編集]