暗黙の型変換で SwingUtilities.invokeLater 呼び出したときに陥った勘違い

Scala で暗黙の型変換を使った際に勘違いではまったところがあったので間違った所を晒しておきます。あんまり同じ勘違いをする人もいないとはいないとは思いますけど。

やりたかったことは

  • ネットワークアクセスが必要な時間のかかる処理をアクターで実行。
  • 処理が終わったら SwingUtilities.invokeLater を使ってコンポーネントを変更

この時、アクターから invokeLater を呼び出す際に Runnable を生成しなければならないのがめんどくさかったので、以下様に、implicit conversion(暗黙の型変換)を行って invokeLater の呼び出していました。

SwingUtilities invokeLater の { } で囲まれたブロックが Runnable の run メソッドに変換されると期待していたのです。が、実際にはこのコードを実行し「Go」ボタンをクリックすると以下のような出力が得られます。

one: false
two: true

つまり、2つ目の println は EDT(Event Dispatcher Thread) で実行されているけど、1つ目の println は EDT では無いスレッドで実行されているということです。どうしてこんなことになっているのか? と思い、生成されたクラスファイルをデコンパイラである JD-GUI を使って Java のソースにデコンパイルしてみました。

  public void myFunc()
  {
    Predef..MODULE$.println(new StringBuilder().append("one: ").append(BoxesRunTime.boxToBoolean(SwingUtilities.isEventDispatchThread()).toString()).toString());

    SwingUtilities.invokeLater(
      functionToRunable(new AbstractFunction0.mcV.sp() { public static final long serialVersionUID = 0L;

      public final void apply() { apply$mcV$sp(); } 
      public void apply$mcV$sp() { Predef..MODULE$.println(new StringBuilder().append("two: ").append(BoxesRunTime.boxToBoolean(SwingUtilities.isEventDispatchThread()).toString()).toString()); }
    }));
  }

「one: ...」を表示する println が見事に SwingUtilities.invokeLater のブロックから外れています。

  implicit def functionToRunable[T](x: => T) : Runnable = new Runnable() { def run = x }

コードの implicit conversion では変換前のソース型が関数リテラルで、それを必要に応じてターゲット型である Runnable に変換するように定義されています。つまり、2つ目の println がこの条件に合致したので、この println だけ型変換が行われた、ということのようです。

対策としては、Runnable の run として実行したいブロックを関数として定義して invokeLater の引数として渡してしまえばいいと思います。

  def printOneTwo : Unit = {
      println( "one: " + SwingUtilities.isEventDispatchThread)
      println( "two: " + SwingUtilities.isEventDispatchThread)      
  }  
  
  def myFunc =  SwingUtilities invokeLater printOneTwo

ただ、これだと元々やりたかった手間が省けてないので、私は結局 implicit conversion を使わずに Runnable を生成して invokeLater に渡すようにしてしまいました・・・・

  def myFunc =  SwingUtilities invokeLater new Runnable(){
    def run = {
      println( "one: " + SwingUtilities.isEventDispatchThread)
      println( "two: " + SwingUtilities.isEventDispatchThread)       
    }
  }

認識が間違っているとか、もっとよいやり方があるよ、とかがありましたらコメントいただけたら嬉しいです。

参照フォーラム: inline implimantation