同期と非同期について、今回は様々なクラスやメソッドと共に解説を行っていきます。
#1.同期と非同期
アクションを順番に実行したい場合、同期で実行するようにコードを書く必要があります。
//animateアクションのビルド
Animate animate = ...;
//animateアクションを同期で実行
animate.run();
//listenアクションのビルド
Listen listen = ...;
//listenアクションを同期で実行
listen.run();
ここではAnimateとListenを同期で順番に実行していますが、これらのアクションは非同期で実行することも可能です。
//animateアクションを非同期で実行
animate.async().run();
//listenアクションを非同期で実行
listen.async().run();
単純にこれらのアクションを非同期で実行してしまうと、アクションが連結していないため双方のアクションが同時に開始されます。一つ目のアクションが終了してから二つ目のアクション(ここではAnimateが終わった後のListen)を開始するよう非同期で実行したい場合には、複数のアクションを連結させる必要があります。
#2.Futures
Futureクラスについて説明します。
Futureとは
Futureクラスは非同期の操作をラップするオブジェクトであり、主として次のような非同期の処理を行うために使用されます。
- アクションやリソースといったオブジェクトの作成
- アクション実行の取り扱い(結果の取得やアクションのキャンセル)
- 複数のアクション実行やリソース作成の連結
Futureの状態
Futureの状態には成功、アクションのキャンセルもしくはエラーの3つがあり、成功した場合には値を返却することがあります。
Futureは戻り値の型にTを対応付けるジェネリッククラスであり、例えばFutureインスタンスであればStringが返却されます。値を返却しないような場合にはFutureを使用します。 また、getメソッドを使用することで同期的な値の取得が可能です。
Future<String> stringFuture = ...;
String value = stringFuture.get();//処理が完了するのを待って値を取得(同期)
getメソッドをコールすると現在のスレッドはブロックされます。 ラップされた操作にエラーが生じた場合にはExecutionExceptionがスローされ、操作がキャンセルされた場合にはCancellationExceptionがスローされるため、Futureの終わりにtry-catchを実装しましょう。
Future<String> stringFuture = ...;
try {
String value = stringFuture.get();
//Futureが値を返して終了
} catch (ExecutionException e) {
//Futureがエラーで終了
} catch (CancellationException e) {
//Futureがキャンセルされて終了
}
#3.連結用のメソッド
Futureクラスは非同期の処理を連結できる複数のメソッドを持っており、前半部分のthen、andThen、後半部分のConsume、Apply、Composeの組み合わせからなります。
- thenConsume
- thenApply
- thenCompose
- andThenConsume
- andThenApply
- andThenCompose
3-1.then / andThen
前半部分のthenとandThenの違いについて説明します。
thenから始まるメソッドは一つ前の処理の結果に関係なく続きの処理を実行したいような連結に向いています。一つ前の処理をラップしたFutureへアクセス可能で、Fututeの状態を取得するにはisSuccess、hasError、isCancelledメソッドを使用してください。
Future<string>future = ...;
future.thenConsume(stringFuture -> {
if (stringFuture.isSuccess()) {
//成功した場合の処理
//stringfuture.get()で値へアクセス
} else if (stringFuture.isCancelled()) {
//キャンセルされた場合の処理
} else {
//エラーになった場合の処理
//stribgFuture.getError()でエラーへアクセス
}
});
andThenから始まるメソッドでは一つ前の処理が成功した場合にのみ続きの処理を連結することが出来、複数の処理が一つ前の処理に依存している場合の使用に向いています。最初の処理をラップしたFutureの値へ直接のアクセスが可能です。
Future<string>future = ...;
future.andThenConsume(string -> {
});
andThenから始まるメソッドはひとつ前の処理が成功した場合にのみ実行されますので、Boolean型 を && で連結して評価するようなイメージに近いです。
連結用のメソッドの戻り値
then...、andThen...の戻り値は連続した2つの処理をラップしたFutureです。
thenから始まるメソッドの使用例
then..を使ってPepperに複数のモーションを非同期で順番に実行させたい場合は、以下のソースコードを参照してください。
//animateアクションのビルド
Animate firstAnimate = ...;
//animateアクションを非同期で実行
Future<void> firstDance = firstAnimate.async().run();
//複数のアニメーションを連結
Future<void>ballet = firstDance.thenCompose(dance -> {
//animateアクションのビルド
Animate secondAnimate = ...;
//animateアクションを非同期で実行
Future<void> secondDance = secondAnimate.async().run();
return secondDance;
});
andThenから始まるメソッドの使用例
andThen...を使ってPepperに複数の異なるモーションを非同期で順番に実行させたい場合は、以下のソースコードを参照してください。
//animateアクションのビルド
Animate animate = ...;
//animateアクションを非同期で実行
Future<void> imitation = animate.async().run();
//アニメーションとlistenアクションを連結
Future<listenresult> guess = imitation.andThenCompose(animateFuture -> {
//listenアクションをビルド
Listen listen = ...;
//listenアクションを非同期で実行
Future<listenresult> answerListening = listen.async().run();
return answerListening;
});
3-2.consume / apply / compose
次のメソッドの使用は2つ目の操作の戻り値に依存します。
- ...Consume,
- ...Apply,
- ...Compose.
consume:
非同期で実行する処理に戻り値がない場合にはConsumeで終わるメソッドを使用します。
Future<string>future = ...;
Future<void> returnedOperation = future.andThenConsume(string -> Log.i(TAG, "Success: " + string));
apply:
非同期で実行する処理に戻り値がある場合にはApplyで終わるメソッドを使用します。
Future<string> future = ...;
Future<integer> returnedOperation = future.andThenApply(string -> string.length());
compose:
非同期で実行する処理がFutureを返す場合はComposeで終わるメソッドを使用してください。アクションの連結ではこのメソッドがメインになります。
Say say = ...;
Future<void> sayFuture = say.async().run();
Future<void> returnedOperation = sayFuture.andThenCompose(ignore -> {
Animate animate = ...;
Future<void>animateFuture = animate.async().run();
return animateFuture;
});
#4.コールバック
連結用のメソッドはそれぞれに、パラメータとして異なるコールバックインターフェースを用います。
オペレータ | andThen... | then... |
---|---|---|
...Consume | Consumer | Consumer<Future<T>> |
...Apply | Function | Function<Future<T>,R> |
...Compose | Function<T,Future<R>> | Function<Future<T>,Future<R>> |
4-1.Consumer
ConsumerインターフェースにはFutureの結果の獲得に使用されるconsumeメソッドがあり、これはワーカースレッドで実行されます。使用例は以下のとおりです。
andThenConsumeを使ってFutureの結果のログ出力
//Java8
Future<string>future = ...;
future.andThenConsume(string -> Log.i(TAG, "Success: " + string));
//Java7
Future<string>future = ...;
future.andThenConsume(new Consumer<string>() {
@Override
public void consume(String string) throws Throwable {
Log.i(TAG, "Success: " + string);
}
});
4-2.Function
Functionインターフェースには新規の戻り値もしくはFutureを返すexecuteメソッドがあり、ワーカースレッドで実行されます。使用例は以下のとおりです。
andThenApplyを使ってFutureの結果を変換
//Java8
Future<string> future = ...;
future.andThenApply(string -> string.length());
//Java7
Future<string> future = ...;
future.andThenApply(new Function<String, Integer>() {
@Override
public Integer execute(String string) throws Throwable {
return string.length();
}
});
andThenComposeを使ってアクションの連結
//Java8
Animate animate = ...;
animate.async().run().andThenCompose(ignore -> {
Say say = ...;
return say.async().run();
});
//Java7
Animate animate = ...;
animate.async().run().andThenCompose(new Function<Void, Future<Void>
>() {
@Override
public Future<Void> execute(Void ignore) throws Throwable {
Say say = ...;
return say.async().run();
}
});