JRuby+swingでinvokeLaterを使ってみる
SwingUtilities.invokeLaterというものを自分なりに理解してみました。
swingのスレッドについて
Swingのスレッドは、通常スレッドセーフではありません。また、Swingには次のようなスレッドポリシーがあります。従わないと最悪デッドロックが発生することもあるようです。
- コンポーネントの「状態に依存する」あるいは「状態に影響を与える」 処理はすべてEDT(イベントディスパッチスレッド)上で動作しなければならない
簡単にいうと描画に関しては1つのスレッド(EDT)上で処理しなければならないということのようです。
サンプルコード
EDTを理解するに当って以下のプログラムを用意してみました。
1つはEDTを使わなかった場合の2つのスレッド実行、もう1つはinvokeLaterを使った場合の実行を示します。
以下ではボタンをクリックすると2つのスレッド上でCountクラスのrunが実行されて、合計で(期待値=)200となるプログラムです。
- 「Count.new(label).run」でカウントした場合(正しく200までカウントされない)
※該当するコードをコメント箇所を修正してください。
- 「SwingUtilities.invoke_later Count.new(label)」でカウントした場合(正しく200までカウントされている)
require 'java' %w( JFrame JPanel JLabel JButton SwingUtilities ).each{|c| eval c+"=javax.swing."+c} class Count def initialize model @model = model end def run 100.times do n = @model.text.to_i @model.text = (n + 1).to_s end end end def run label = JLabel.new "0" button = JButton.new "Push" button.add_action_listener do |event| Thread.start do # Count.new(label).run SwingUtilities.invoke_later Count.new(label) end Thread.start do # Count.new(label).run SwingUtilities.invoke_later Count.new(label) end end panel = JPanel.new panel.add label panel.add button frame = JFrame.new "invokeLater sample" frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.content_pane.add panel frame.set_size 150,80 frame.location_relative_to = nil frame.visible = true end SwingUtilities.invoke_later self
「Count.new(label).run」で正しく200までカウントされない原因は、説明は省きます。ここが重要なんだと思いますが。。
失敗したこと
ボタンクリックしたときの動作記述を以下のように書いた時、うまく動きませんでした。
SwingUtilities.invoke_later do 100.times do n = label.text.to_i label.text = (n + 1).to_s end end
これで実行してしまうと、なぜかフレームが200個?複製されてうまく動作しません。
原因は、よくわかりませんでした。。
感想
今回のサンプルコードでは、おそらく完全には理解できたとは言えないと思いますが、それでも僕にとってはSwingスレッド理解への手助けにはなったと感じています。
とりあえず、ここまで。