JavaFX 1.2 非同期処理の概要(2)

JavaFX 1.2での非同期処理の続きですが、非同期処理の進捗などをGUIに反映する方法です。ただし、今回の範囲は今後のバージョンで改良されると思われます。

非同期処理の結果をコールバック関数で取得する

非同期処理のスレッドとEDTスレッドの間でデータをやりとりする必要があります。
このやり方には幾つかの方法がありますが、もっとも単純な方法は非同期メソッドの戻り値を受け取る方法です。しかし、RunnableFutureのrun()メソッドはvoidである為、Callableのように戻り値をオブジェクトとすることはできません*1
このままでは、非同期処理の結果をGUI等に反映する事ができませんので、共通のオブジェクトを使用し、お互いに参照する方式をとります。
最初に作成するクラスはスレッド間でデータを保持するホルダークラスです。

import java.util.concurrent.atomic.AtomicReference;

// Thread safe class
public class MyLongTaskResultHolder {
    private final AtomicReference<String> result
                          = new AtomicReference<String>();
    public String getResult() {
        return result.get();
    }
    public void setResult(String result) {
        this.result.set(result);
    }
}

尚、このクラスは異なるスレッド間でお互いに変更される可能性があります。したがって、参照のAtomic性を保証し、スレッドセーフにする必要があります。

続けて処理クラスにデータホルダーを参照として定義します。単純にコンストラクタの引数を追加し、フィールドが追加されただけです。また、処理が終わった時、ホルダーに文字列を渡しています。

public class MyLongWorker implements RunnableFuture {

    private final MyLongTaskResultHolder result;
    public MyLongWorker(MyLongTaskResultHolder result) {
        this.result = result;
    }
    @Override
    public void run() throws Exception {
        System.out.println("MyLongTask start.");
        try {
            Thread.sleep(5 * 1000); // sleep 5s
        } catch (InterruptedException e) { // do nothing
        }
        result.setResult("Complete!");
        System.out.println("MyLongTask end.");
    }
}

次にFX側のタスクです。インスタンス変数にデータホルダーを定義し、create()時にMyLongWorkerのコンストラクタに渡しています。また、resultを参照するためにgetResult関数を定義しました。

public class MyLongTask extends JavaTaskBase {
    var result: MyLongTaskResultHolder;
    // return RunableFuture implement
    override function create(): RunnableFuture {
        result = new MyLongTaskResultHolder();
        return new MyLongWorker(result);
    }
    public function getResult():String {
        return result.getResult();
    }
}

最後にonDoneのコールバック関数で、タスクから結果を取得します。ここで{this.getResult()}と書きたくなるところですが、このコールバック関数はインスタンス関数ではありません。直接インスタンスにアクセスする事は出来ないため、インスタンスへの参照である変数myLongTaskからインスタンス関数を呼び出しています。

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

ポイントはどのクラスどのスレッドで実行されるかを意識し、複数のスレッドでまたがるように使用されるクラスをスレッドセーフにしなければなりません。スレッドセーフに関する基礎は「Java並行処理プログラミング」を読むこと。

明日は、並列処理のラストとして長い処理の進捗を通知するようにしてみます。

*1:ここは改善される可能性はあるかもしれません。Callableのように戻り値を定義できるようにした上で、Taskにresultプロパティなどが定義されるかもしれません。要はSwingWorker風にならないかな、と思います