JavaFX 1.2 非同期処理の概要

はじめにお断り。JavaFX1.1から色々と変更のあった箇所の1つであり、まだまだ改良中なようで、バージョンアップ時には変わっていると思います。

非同期処理が必要な理由

JavaFXも一般的なGUIと同じようにシングルスレッドモデルで動いています。つまり、唯1つのスレッドがGUIの描画に関して責任を持ちます。このスレッドはSwingと同様にEDT(Event Dispacher Thred)と呼ばれます。
このシングルスレッドモデルによるGUIは、設計しやすくバグを生みにくい反面、長い処理が含まれているとその間はGUIの描画が行われなくなるという性質を伴います。
この現象はあまり品質の良くないアプリケーションでは遭遇したことがあるでしょう。ボタンを押してから反応がなくなり処理が終わった後にGUIが操作できるような不具合です。この不具合の原因はEDTで長く時間のかかる処理を行っている事です。
EDTで長い処理を行わない為には、長い処理(例えばDBからデータを取得するなど)を非EDTで並列処理させる事が必要です。この非EDTで処理をさせる事こそ(GUIの)非同期処理です。

javafx.asyncパッケージ

javafx.asyncパッケージにはJavaFXで非同期処理を行うためのクラスが定義されています。

Task

JavaFX 1.2ではTaskクラスは直接使用する事はないと思いますが、非同期処理の基本クラスです。後述のJavaTaskBaseやHttpRequestのスーパークラスになっています。

JavaTaskBase

JavaFX 1.2ではユーザ定義の非同期処理を作成する場合、JavaTaskBaseのサブクラスを作成し、Javaのクラスとして非同期処理を行うクラスを作成することになります。

RunnableFuture

RunnableFutureはJavaインターフェイスであり、Runnableとほぼ同様の意味を持ちます。すなわち、run()メソッドが定義されており、run()メソッドを実装したJavaクラスを定義する事で非同期処理を実現します。
Runnableとの違いはrun()メソッドの例外のシグニチャでExceptionをThrowsする事が出来る点です。Java5で導入されたconcurrentフレームワークのCallableとRunnableの中間的なインターフェイスと考えてください*1

非同期処理クラスを作成する

はじめにRunnableFutureをimplementしたJavaのクラスを作成します。このクラスには非同期に行う長い処理を記述します。とりあえず長い処理をシミュレートしたMyLongWorkerクラスを定義してみましょう。

import javafx.async.RunnableFuture;

public class MyLongWorker implements RunnableFuture {
    @Override
    public void run() throws Exception {
        System.out.println("MyLongTask start.");
        try {
            Thread.sleep(5 * 1000); // sleep 5s
        } catch (InterruptedException e) { // do nothing
        }
        System.out.println("MyLongTask end.");
    }
}

しつこいようですが、これはJavaのクラスです。また、JavaFX 1.2ではユーザ定義の非同期処理クラスはJavaを使って記述する必要がありますが、今後改善されていくと思われます。

非同期処理を実行するFXクラスを作成する

次にJavaTaskBaseを継承したFXクラスを作成します。このクラスは非同期処理を管理するクラスで、実際に非同期処理を行うスレッドの監視や処理の開始・終了・例外時のハンドリングなどを定義します。最低限の実装では、create()メソッドをオーバライドし、RunnableFutureの実装クラスMyLongWorkerのインスタンスを返すだけです。

import javafx.async.JavaTaskBase;
import javafx.async.RunnableFuture;

public class MyLongTask extends JavaTaskBase {
    // return RunableFuture implement
    override function create(): RunnableFuture {
        return new MyLongWorker();
    }
}

非同期処理を実行する

以上で非同期処理を行う準備は整いました。後は非同期処理を実行するだけですが、非同期処理の開始時と終了時に、呼び出されるコールバック関数を指定する事ができます。

var text:String;
var myLongTask  = MyLongTask {
  onStart: function() {
      text = "onStart";
  }
  onDone: function() {
      text = "onDone";
  }
};

尚、ここで指定する関数はあくまで関数な為、内部でthisを使ったインスタンス変数へのアクセスやインスタンス関数を呼び出すことができません*2

後はTaskを開始させるだけです。

myLongTask.start();

Stageに反映する

後はbindとTextを使って簡単なGUIを書きましょう。

Stage {
    title: "Async Sample1"
    scene: Scene {
        width: 100, height: 50
        content: [
            Text {
                textOrigin: TextOrigin.TOP
                content: bind text
            }
        ]
    }
}

まとめ

現時点ではJavaのクラスを作成しないと非同期処理を実装できませんが、これは次回以降のバージョンで改善されていくでしょう。
非同期処理で重要なことは、実際に行われる非同期な処理、非同期な処理をどうコントロールするか(コールバックなど)、実際に裏側で処理するスレッドの管理、をいかにきれいに設計できるかと思います。JavaFXでは処理と管理を分離することですっきりとした設計が可能です。
次回は処理結果の渡し方と、進捗状況をどう管理するかを説明します。

ソースコード

MyLongWorker.java
package jp.deathmarch.javafx.sample.async;

import javafx.async.RunnableFuture;

public class MyLongWorker implements RunnableFuture {
    @Override
    public void run() throws Exception {
        System.out.println("MyLongTask start.");
        try {
            Thread.sleep(5 * 1000); // sleep 5s
        } catch (InterruptedException e) { // do nothing
        }
        System.out.println("MyLongTask end.");
    }
}
MyLongTask.fx
package jp.deathmarch.javafx.sample.async;

import javafx.async.JavaTaskBase;
import javafx.async.RunnableFuture;
import jp.deathmarch.javafx.sample.async.MyLongWorker;

public class MyLongTask extends JavaTaskBase {

    // return RunableFuture implement
    override function create(): RunnableFuture {
        return new MyLongWorker();
    }
}
Main.fx
/*
 * Main.fx
 *
 * Created on 2009/08/01, 19:52:06
 */

package jp.deathmarch.javafx.sample.async;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;

import javafx.scene.text.TextOrigin;

var text:String;
var myLongTask  = MyLongTask {
  onStart: function() {
      text = "onStart";
  }
  onDone: function() {
      text = "onDone";
  }
};
myLongTask.start();

Stage {
    title: "Async Sample1"
    scene: Scene {
        width: 100, height: 50
        content: [
            Text {
                textOrigin: TextOrigin.TOP
                content: bind text
            }
        ]
    }
}

*1:最終的にはCallableに統一されそうだけど・・・どうなんだろう?

*2:呼び出す方法は次回説明します