ついにベールを脱いだJavaFX

第11回 通信と非同期処理

この記事を読むのに必要な時間:およそ 12 分

もう1つの非同期処理用クラス

RemoteTextDocumentクラスもHttpRequestクラスも通信を非同期に行うためのクラスでした。では,通信以外に非同期を行う場合はどうすればよいのでしょう。たとえば,DBへのアクセスや,計算に時間がかかるロジックなどは非同期で行う方が,パフォーマンスの低下を防ぐことができます。

非同期に処理を行うには,2つの方法があります。1つの方法は前節で紹介したSwingWorkerクラスを使用する方法,もう1つの方法がjavafx.async.AbstractAsyncOperationクラスを使用する方法です。ここでは,AbstractAsyncOperationクラスを使用してみましょう。

AbstractAsyncOperationクラスは名前からもわかるようにアブストラクトクラスなので,サブクラスを作成する必要があります。たとえば,RemoteTextDocumentクラスがAbstractAsyncOperationクラスのサブクラスです。

AbstractAsyncOperationクラスは,非同期で処理する部分をJavaで記述する必要があります。このため,ちょっと使いにくくなってしまっています。しかし,ロジック部分をJavaで記述するようなアプリケーションであれば,すでにJavaを使用しているので,すぐに使うことができるはずです。

Java側ではcom.sun.javafx.runtime.async.AbstractAsyncOperationクラスのサブクラスを作成します。JavaFXとJavaではクラス名が同じで,パッケージが異なるだけなので,注意してください。

JavaのAbstractAsyncOperationクラスはConcurrency Utilitiesに含まれるCallableインターフェースを実装しています。しかし,Callableインターフェースが定義しているcallメソッドは実装されていないので,AbstractAsyncOperationクラスのサブクラスがcallメソッドを定義しなくてはなりません。

Javaで非同期処理を行う場合,Runnableインターフェースを実装したクラスを作成するのが一般的ですが,Runnableインターフェースのrunメソッドは戻り値がありません。そのため,非同期で行った処理の結果を返すことが簡単にはできません。一方のCallableインターフェースのcallメソッドは戻り値を持たせることができます。戻り値の型はジェネリクスでパラメータ化されています。

そのため,Callableインターフェースを実装するAbstractAsyncOperationクラスもパラメータ化されています。JavaFXはジェネリクスがないので,ジェネリクスを無視してもいいのですが,コンパイル時に警告が出ます。警告が気になるのであればパラメータを指定した方がいいですね。

あまり意味はないのですが,ここではスレッドを一定期間スリープさせ,スリープから起きた時間をJavaFXのイベントディスパッチスレッドに返すようにしてみました。

リスト8

public class LongLongTaskImpl extends AbstractAsyncOperation<Date> {
    public LongLongTaskImpl(AsyncOperationListener<Date> listener) {
        super(listener);
    }
      
    // 非同期にコールされるメソッド
    @Override
    public Date call() {
        try {
            // 10秒スリープ
            Thread.sleep(10000L);
        } catch (InterruptedException ex) {
            // 非同期処理のキャンセルが行われると,
            // InterruptedException例外が発生する
            // このため,InterruptedException例外が発生した時に
            // 速やかにcallメソッドを抜け出る必要がある
        }

        // 現在時刻を返す
        return new Date();
    }
}

LongLongTaksImplクラスのコンストラクタの引数であるAsyncOperationListenerインターフェースは,非同期の処理が終わった時や,非同期処理のキャンセル時にコールされるコールバックメソッドを定義しているリスナです。しかし,AbstractAsyncOperationクラスが内部的に使用するだけなので,あまり気にする必要はありません。

非同期処理はcallメソッドに記述します。callメソッドの戻り値はクラス定義のジェネリクスパラメータと同一にします。ここでは,java.util.Dateクラスを用いています。

callメソッドでは10秒間スリープした後,現在時刻をDateオブジェクトとして返しています。

重要なのはInterruptedException例外の扱いです。非同期処理がキャンセルされた場合,ブロックしている処理に対してInterruptedException例外が発生します。そのため,InterruptedException例外を適切にキャッチしてcallメソッドを抜けるようにしないと,いつまでたっても処理が終わらいままになってしまいます。

では,次にJavaFX側に移りましょう。

リスト9

public class LongLongTask extends AbstractAsyncOperation {
    // 非同期処理の結果
    public var result: Date;
  
    // 非同期処理を行うJavaクラス
    var task: LongLongTaskImpl;
  
    ublic override function start(): Void {
        // 非同期処理の開始
        task = new LongLongTaskImpl(listener);
        task.start();
    }
  
    public override function cancel(): Void {
        // 非同期処理のキャンセル
        task.cancel();
    }
     
    public override function onCompletion(value: Object): Void {
        // valueがLongLongTaskImpl#callメソッドの戻り値
        result = value as Date
    }
}

JavaFXのAbstractAsyncOperationクラスはstart関数,cancel関数,onCompletion関数の3つの関数をオーバーライドする必要があります。

start関数はJavaのAbstractAsyncOperationクラスのサブクラスを生成して,非同期処理を開始させます。cancel関数はキャンセル処理を記述します。

最後のonCompletion関数は非同期処理が完了した後にコールされるコールバック関数です。onCompletion関数の引数には,JavaのAbstractAsyncOperationクラスのサブクラスのcallメソッドの戻り値が代入されます。JavaFXではジェネリクスはないため,引数の型はObjectクラスになります。

LongLongTaskImplクラスのコンストラクタで指定したAsyncOperationListenerインターフェースは,JavaFXのAbstractAsyncOperationがlistenerアトリビュートとして保持しています。そこで,青字のように,そのままLongLongTaskImplオブジェクトに渡してしまいます。

非同期処理を開始するのはAbstractAsyncOperationクラスのstartメソッドです。そこで,LongLongTaskImplオブジェクトを生成したら,startメソッドをコールします。

キャンセル処理はそのままLongLongTaskImplオブジェクトのcancelメソッドをコールするだけです。onCompletion関数ではcallメソッドの戻り値であるDateオブジェクトをアトリビュートとして保持させています。

著者プロフィール

櫻庭祐一(さくらばゆういち)

横河電機に勤務するかたわらJava in the Boxにて新しい技術を追い続けています。JavaOneは今年で11年目。名実共にJavaOneフリークと化しています。