JavaFX Advent Calendar 2012 2日目 javafx.concurrent.Task

このエントリはJavaFX Advent Calendar 2012の2日目です。
1日目は@aoetkさんのJavaFXでのマルチタッチアプリケーション開発でした。
まだまだ余裕があるので、JavaFXに興味のある方/持った方はぜひ登録してください!


さてさて、4年間付き合ったSwingとは仕事での縁がなくなり、RunnableやFutureといったクラスをめっきり見なくなってしまいました。
ですが!JavaFXなどのGUIアプリケーションでは、快適に動作するアプリケーションを開発する(画面描画をストップさせない)ために複数のスレッドを使用した非同期処理が非常に重要です。
そんなわけで、javafx.concurrentパッケージのTaskクラスについて紹介します。


さてこのTaskクラスですが、ざっくり説明すると非同期に実行する処理を実装するためのクラスです。
このクラスは抽象クラスになっており、callメソッドが抽象メソッドとして宣言されているので、この中に実際に非同期で実行する処理を実装します。
なお、Javadocでも触れられていますが、callメソッドはJavaFXの描画スレッドとは別のスレッドで実行されることになるので、このメソッド内で既にアクティブな状態のJavaFXのUIコントロール等を操作してはいけません。


例えば、Backlogというプロジェクト管理サービスから特定のユーザの課題一覧を取得する場合、このようなコードになります。

public class GetIssueTask extends BacklogTaskBase<List<Issue>> {

    @Inject
    private BacklogClient backlogClient;
    @Inject
    private User user;

    @Override
    protected List<Issue> call() throws Exception {
        List<Project> projects = backlogClient.getProjects().execute();

        final List<Issue> issueList = new ArrayList<>();

        for (Project project : projects) {
            List<Issue> findIssues = backlogClient.findIssue()
                    .setProjectId(project.getId())
                    .addAssignerId(user.getId())
                    .execute();

            issueList.addAll(findIssues);
        }

        return issueList;
    }
}

このGetIssueTaskを実行するには、このクラスのインスタンスをスレッド、またはjava.util.concurrentパッケージのExecutorServiceに渡して実行します。

Thread th = new Thread(new GetIssueTask());
th.setDaemon(true);
th.start();

または

Executors.newSingleThreadExecutor().submit(new GetIssueTask());


さて、これだけだとRunnableクラスやFutureTaskクラスを使うのと同じでなんのありがたみもありません。
以上のクラスとの違いは、TaskクラスがJavaFXで非同期処理を実行する時に便利なプロパティをいくつか持っていることです。
Javadocより抜粋すると以下の14個です。

  • ReadOnlyObjectProperty exception
  • ReadOnlyStringProperty message
  • ObjectProperty> onCancelled
  • ObjectProperty> onFailed
  • ObjectProperty> onRunning
  • ObjectProperty> onScheduled
  • ObjectProperty> onSucceeded
  • ReadOnlyDoubleProperty progress
  • ReadOnlyBooleanProperty running
  • ReadOnlyObjectProperty state
  • ReadOnlyStringProperty title
  • ReadOnlyDoubleProperty totalWork
  • ReadOnlyObjectProperty value
  • ReadOnlyDoubleProperty workDone

ご覧の通り、これらのプロパティはすべてjavafx.beans.propertyで宣言されているため、これらのプロパティ自体をJavaFXのUIコントロールのプロパティとバインドして値を表示したり、コールバックハンドラを登録することが可能です。便利ですね!!


また、これらのプロパティを更新するためにupdateProgressやupdateMessageといったメソッドも定義されています。
これらのメソッドは内部でPlatform.runLaterを呼び出して更新を行います。どういうことかというと、JavaFXの描画スレッドではないスレッドで実行されるcallメソッド内で、スレッドを意識せずに直接呼び出すことが可能です。

public class GetIssueTask extends BacklogTaskBase<List<Issue>> {

    @Inject
    private BacklogClient backlogClient;
    @Inject
    private User user;

    @Override
    protected List<Issue> call() throws Exception {

        List<Project> projects = backlogClient.getProjects().execute();

        final List<Issue> issueList = new ArrayList<>();

        int progressMax = projects.size();
        int progress = 0;
        for (Project project : projects) {
            List<Issue> findIssues = backlogClient.findIssue()
                    .setProjectId(project.getId())
                    .addAssignerId(user.getId())
                    .execute();

            issueList.addAll(findIssues);

            // 進捗率を更新 
           	// ProgressBarのprogressプロパティ同士バインドするとプログレスバーが進捗に応じて更新される!
            updateProgress(progress, progressMax); 
        }

        return issueList;
    }
}

javafx.concurrentにはもうひとつ、Serviceという便利なクラスもあるのですが、良い感じの長さのエントリになったのでこちらは又の機会or本日以降の人ということで。

参考サイト
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html