ユーザーインターフェイススレッド
ユーザーインターフェイススレッド (User interface thread) とは、グラフィカルユーザインタフェースでのメインスレッドの事。本項では、GUIでのマルチスレッドに関するデザインパターンを記載する。ユーザーインターフェイススレッドは Java ではイベントディスパッチスレッド、Adobe Flashではprimordial workerと呼ばれる。
目次 |
歴史的経緯[編集]
GUIにおいて、基本的に 60fps で処理しないといけないが、I/O処理や画像・動画のデコードなど処理に時間がかかる物があり、1つのスレッドだけで実行すると、その処理を実行している間に、UIの反応がなくなってしまう。それを防ぐためにマルチスレッド化が必要だが、それをどのように持ち込むかが歴史的には1980年代から色々と議論があった。
例えば、Java の AWT では、1996年の最初の時点は、単純にスレッド間でデータ共有型のマルチスレッドになっていた。しかし、データ共有するには、ロックをかけないといけないが、親コンポーネントから子コンポーネントを呼んだり、コールバックで子から親を呼んだり、アプリケーションからGUIライブラリを呼んだり、GUIライブラリからアプリケーションをコールバックしたりと、双方向に呼び出すことが多く、異なるスレッド間で双方向に呼び合うときは、ロックの順番に注意を払う必要があるが、非常に複雑になり、デッドロックは常にではなくたまに発生したりすることの多いバグであり、バグ取りが大変になるという問題があった[1]。
そこで、1997年の Java の Swing からは、UI の操作は全てメインのUIスレッドであるイベントディスパッチスレッドから操作しなくてはならない、というルールを設けた。そして、2006年の Java 6 から、UI で重い処理をするために、ワーカーデザインパターンを採用した javax.swing.SwingWorker を搭載した。
現在では、多くのUIライブラリが、UIスレッドに操作を限定+ワーカーデザインパターンの組み合わせを採用している。
UIスレッドへの委譲[編集]
UIスレッド以外からUIスレッドに操作を依頼(委譲)するのが、まず必要である。
Java[編集]
Java の場合は、以下の方法で、UI スレッドに委譲できる。
- 同期処理 -
SwingUtilities.invokeAndWait(Runnable),EventQueue.invokeAndWait(Runnable) - 非同期処理 -
SwingUtilities.invokeLater(Runnable),EventQueue.invokeLater(Runnable)
同期処理は、処理が完了するまで待つ。
他のライブラリ[編集]
System.Windows.Forms.Control.Invoke()- .NET FrameworkActivity.runOnUiThread(Runnable),View.post(Runnable),View.postDelayed(Runnable, long)- Android
ワーカーデザインパターン[編集]
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) } }
他のライブラリ[編集]
System.ComponentModel.BackgroundWorker- .NET Frameworkflash.system.Worker- Adobe Flashandroid.os.AsyncTask- Android
同一インスタンスのワーカーを2回以上実行したときに、それは、逐次処理するべきなのか、並列処理するべきなのかという議論があり、UIライブラリによって様々であるが、Androidにおいては、2009年9月のAndroid 1.6までは逐次処理、2009年11月のAndroid 2.0からは並列処理、2011年2月のAndroid 3.0からはデフォルトは逐次処理と、色々と変遷した[2]。並列処理にすると使うときにバグを生みやすくなると言うのが、逐次処理に戻した理由である。なお、ワーカー自体が逐次処理であっても、複数のワーカーインスタンスを作り、別々に実行すれば並列に処理できる。
タイマー[編集]
UIスレッドで呼び出されるタイマーも多くのライブラリで備わっている。
javax.swing.Timer - JavaSystem.Windows.Forms.Timer- .NET Frameworkflash.utils.Timer- Adobe Flash