前回、「関数描画アプリケーション」で Charts プラグインを使ってグラフを描画するようにしました。 ついでにサンプル数も変更できるようにしました。 で、調子に乗ってサンプル数を 10,000 とか 100,000 とかにしてグラフを描かせていると見事にフリーズ・・・ ってことで、これを改善すべく、今まで敢えて(?)目を背けてたスレッド関連の話にそろそろ足を踏み入れてみようかと*1(一覧)。
さて、「Griffon Guide」のスレッドの部分を見ていくと、「長い計算は Event Dispach Thread (EDT) の外で行うべし」と書かれてます・・・ つーか「EDT」ってナニ? まず Swing のスレッドの扱いから復習した方がよさそうダネ。
Swing でのスレッド
えーと、「Griffon Guide」から張られているリンク「Concurrency in Swing」は断空されているので、Oracle のサイトからそれっぽいのを検索。 The Java Tutorials 「Concurrency in Swing」ってページがあったのでこれでしょう。 ここを見ると、Swing では次の3種類のスレッドがあると書かれています:
Griffon では Initial Threads を気にする必要はないでしょう(たぶん)。 Background Threads は javax.swing.SwingWorker クラスを用いて処理が実装されるためか Worker Threads とも呼ばれるようです。 ただし、Griffon では(少なくとも直接は)SwingWorker を使わないので、以降この名前は使いません。
ってことで、Griffon で大切なのはとりあえず EDT のようですね。 EDT のドキュメントは1ページのみありますが(→)、「Griffon Guide」のスレッドの箇所の方が分かりやすいのでそちらへいきましょう。
Griffon でのスレッド・プログラミング
Griffon Guide 「9. Threading」を読むと、大切なのは次の黄金律だそうです:
all long computations must be performed outside of the Event Dispatch Thread (or EDT for short).
全ての長い計算は EDT の外で行うべし
「EDT の外」っていうのは Swing の Background Threads ですね。 この黄金律は逆に言えば次のようなルールにもなるようです:
all interaction with UI components must be done inside the EDT, including building a component and reading/writing component properties.
全てのUI コンポーネントとの相互作用(コンポーネントの構築、コンポーネントのプロパティの読み書きを含む)は EDT の中で行われるべし
さてこれらを踏まえて、Griffon では以下のようなメソッドが使用できます(通常、これらを使用するのは Controller 内部でしょう)。 Griffon 0.9.5 以降を使う場合は「追記 その1」参照:
EDT 内/外 | 同期/非同期 | Swing | Swing 以外 |
---|---|---|---|
EDT 内 | 同期 非同期 |
edt doLater |
execSync execAsync |
EDT 外 | - | doOutside | execOutside |
これらはすべてクロージャを引数にとり、その内容を上記の方法で実行します。 ちなみに、Swing 以外の UI toolkit では、EDT のことを UI Thread と呼ぶようで*2、EDT 内外は UI Thread 内外となります。
サンプルコード
Griffon 内で上記のメソッドを使用する方法は、概ね以下のようになります(Swing を使用する場合)。 Griffon 0.9.2 以降を使う場合は「追記 その2」を参照:
class MyController { def model def action = { event = null -> // (1) デフォルトでは EDT 内 def value = model.value // (2) model などから値を取得 doOutside { // EDT 外で処理 // (3) コストのかかる処理 doLater { // EDT 内で処理 model.result = … // (4) model などへ値を設定 } } } }
- (1) Controller に記述したイベント処理は、大抵の場合 EDT 内で処理が始まります(むしろそうすべき。)
- (2), (4) model のプロパティは通常 View とバインドされるため、model のプロパティの読み書きは EDT 内で行う必要があります。 まぁ、view のプロパティは言うまでもなく EDT 内で読み書きする必要があるでしょうね。
- (3) コストのかかる計算は EDT 外で行います(doOutside)。 この計算に model のプロパティなどを使用したい場合は、(2) のように EDT 内で値をローカル変数にコピー*3してから、それを doOutside 内で使用します。
まぁ、基礎固めはこんなところで。
関数描画アプリケーションへの適用
は次回に。
追記 その1
Griffon 0.9.5 から、Swing 以外での各種実行メソッドの名前が変更になってます:
EDT 内/外 | 同期/非同期 | Swing | Swing 以外 |
---|---|---|---|
EDT 内 | 同期 非同期 |
edt doLater |
execInsideUISync execInsideUIAsync |
EDT 外 | - | doOutside | execOutsideUI |
追記 その2
Griffon 0.9.2 以降では、アクションを定義するクロージャがデフォルトでは EDT 外で実行されるように変更されました。 したがって、上記のコードは以下のようになります:
class MyController { def model def action = { event = null -> // (1) 0.9.2 以降、デフォルトでは EDT 外 def value = null edt{ value = model.value // (2) model などから値を取得 } // (3) コストのかかる処理 doLater { // EDT 内で処理 model.result = … // (4) model などへ値を設定 } } }
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (199件) を見る