await() / Deferred
| 対応: | Kotlin 1.3(2018) |
|---|
Kotlinコルーチンの『Deferred<T>』は非同期処理の結果を保持するオブジェクトで、『await()』を呼ぶと結果が出るまでコルーチンを中断して待機します。
構文
// async で Deferred を取得
val deferred: Deferred<型> = async {
// 非同期処理
結果 // 戻り値
}
// await で結果を取得(コルーチン内でのみ呼び出せます)
val result: 型 = deferred.await()
// awaitAll で複数の Deferred を一括待機
val results: List<型> = awaitAll(deferred1, deferred2, deferred3)
メソッド一覧
| メソッド / プロパティ | 概要 |
|---|---|
| deferred.await() | 結果が出るまでコルーチンを中断して結果を返します。 |
| awaitAll(vararg deferred) | 複数の Deferred を並行待機して結果のリストを返します。 |
| deferred.isCompleted | 処理が完了しているか確認します。 |
| deferred.isCancelled | キャンセルされているか確認します。 |
| deferred.cancel() | 非同期処理をキャンセルします。 |
| deferred.getCompleted() | 完了済みの場合に結果を取得します(未完了なら例外)。 |
サンプルコード
sample_await_deferred.kt
import kotlinx.coroutines.*
suspend fun fetchUser(id: Int): String {
delay(200)
return "User-$id"
}
suspend fun fetchScore(id: Int): Int {
delay(150)
return id * 100
}
fun main() = runBlocking {
// 2つの非同期処理を並行して起動する
val userDeferred = async { fetchUser(1) }
val scoreDeferred = async { fetchScore(1) }
// それぞれの結果を await で取得する
val user = userDeferred.await()
val score = scoreDeferred.await()
println("$user: ${score}点") // User-1: 100点
// awaitAll で複数の結果を一括取得する
val deferreds = (1..3).map { id ->
async { fetchUser(id) }
}
val users = awaitAll(*deferreds.toTypedArray())
println(users) // [User-1, User-2, User-3]
// isCompleted で完了チェック
val d = async { delay(500); "done" }
println(d.isCompleted) // false(まだ実行中)
d.await()
println(d.isCompleted) // true
}
await_deferred.kt
kotlinc await_deferred.kt -include-runtime -d await_deferred.jar java -jar await_deferred.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) User-1: 100点 [User-1, User-2, User-3] false true
よくあるミス
NG例1: 『async』 の直後にすぐ 『await()』 を呼ぶと順次実行になる。各 async が完了を待ってから次に進むためです。
mistake1_ng.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
val start = System.currentTimeMillis()
val r1 = async { delay(100); "呪術師: 五条悟" }.await() // 100ms 待つ
val r2 = async { delay(100); "呪術師: 虎杖悠仁" }.await() // さらに 100ms 待つ
println("${System.currentTimeMillis() - start}ms") // ~200ms(順次)
}
コンパイルして実行すると次のようになります。
kotlinc mistake1_ng.kt -include-runtime -d mistake1_ng.jar java -jar mistake1_ng.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) 207ms
mistake1_ok.kt
// async を全部起動してから await する(並行実行)
import kotlinx.coroutines.*
fun main() = runBlocking {
val start = System.currentTimeMillis()
val d1 = async { delay(100); "呪術師: 五条悟" }
val d2 = async { delay(100); "呪術師: 虎杖悠仁" }
val d3 = async { delay(100); "呪術師: 伏黒恵" }
println(d1.await())
println(d2.await())
println(d3.await())
println("${System.currentTimeMillis() - start}ms") // ~100ms(並行)
}
コンパイルして実行すると次のようになります。
kotlinc mistake1_ok.kt -include-runtime -d mistake1_ok.jar java -jar mistake1_ok.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) 呪術師: 五条悟 呪術師: 虎杖悠仁 呪術師: 伏黒恵 104ms
NG例2: 未完了の 『Deferred』 に 『getCompleted()』 を呼ぶと 『IllegalStateException』 が発生する。
import kotlinx.coroutines.*
fun main() = runBlocking {
val d = async { delay(300); "完了" }
println(d.getCompleted()) // IllegalStateException: This job has not completed yet
}
修正後は次の通りです。
// await で完了を待ってから getCompleted を呼ぶ
import kotlinx.coroutines.*
fun main() = runBlocking {
val d = async { delay(300); "完了" }
d.await()
println(d.getCompleted()) // 完了
}
NG例3: 『await()』 をコルーチン外(通常の関数)で呼ぶとコンパイルエラーになる。
import kotlinx.coroutines.*
fun fetchResult(d: Deferred<String>): String {
return d.await() // コンパイルエラー: Suspend function 'await' should be called only from a coroutine or another suspend function
}
修正後は次の通りです。
// suspend 関数として定義する
import kotlinx.coroutines.*
suspend fun fetchResult(d: Deferred<String>): String {
return d.await() // suspend 関数内なので呼び出せる
}
概要
複数の『async』を起動してから後でまとめて『await()』を呼ぶことで並列実行が実現します。『async』直後に『await()』を呼ぶと順次実行になるため注意してください。
複数の Deferred をまとめて待つには『awaitAll()』が便利です。いずれか1つが例外を投げると他もキャンセルされます。
コルーチンの起動方法はlaunch / asyncを、タイムアウトはdelay() / withTimeout()を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。