launch / async
| 対応: | Kotlin 1.3(2018) |
|---|
Kotlinコルーチンの『launch』は結果を返さない並行処理の起動に、『async』は結果を返す並行処理の起動に使います。どちらも『CoroutineScope』内で非同期に実行されます。
構文
// launch — 結果なし(Job を返す)
val job: Job = スコープ.launch {
// 非同期処理(戻り値なし)
}
job.join() // 完了まで待機
// async — 結果あり(Deferred<T> を返す)
val deferred: Deferred<String> = スコープ.async {
"結果" // 戻り値
}
val result = deferred.await() // 結果を取得(中断)
構文一覧
| 関数 | 概要 |
|---|---|
| launch { } | 結果を返さないコルーチンを起動します。『Job』を返します。 |
| async { } | 結果を返すコルーチンを起動します。『Deferred<T>』を返します。 |
| job.join() | Job の完了まで現在のコルーチンを中断します。 |
| job.cancel() | Job をキャンセルします。 |
| deferred.await() | Deferred の結果が出るまで現在のコルーチンを中断します。 |
| Dispatchers.IO | I/O 処理向けのスレッドプールで実行します。 |
| Dispatchers.Default | CPU 集約処理向けのスレッドプールで実行します。 |
サンプルコード
sample_launch_async.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
// launch — 結果を返さない並行処理
val job1 = launch {
delay(300)
println("job1 完了")
}
val job2 = launch {
delay(100)
println("job2 完了")
}
job1.join() // job1 の完了を待つ
job2.join()
// job2 完了(先)、job1 完了(後)の順に出力される
// async — 結果を返す並行処理(並列実行)
val deferred1 = async {
delay(200)
"結果A"
}
val deferred2 = async {
delay(100)
"結果B"
}
// 両方を同時に実行して結果をまとめて取得する
println("${deferred1.await()}, ${deferred2.await()}")
// 結果A, 結果B(約200msで完了)
// Dispatchers.IO で I/O 処理を別スレッドで実行する
val ioResult = async(Dispatchers.IO) {
// ファイル読み込みやネットワーク通信をここで行う
"IOの結果"
}
println(ioResult.await()) // IOの結果
}
launch_async.kt
kotlinc launch_async.kt -include-runtime -d launch_async.jar java -jar launch_async.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) job2 完了 job1 完了 結果A, 結果B IOの結果
よくあるミス
NG例1: async の直後に await() を呼ぶと順次実行になり、並列実行にならない。
mistake1_sequential_async.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
val result1 = async { delay(300); "結果A" }.await() // await() を即座に呼んでいる
val result2 = async { delay(300); "結果B" }.await() // 合計 600ms かかる
println("$result1, $result2")
}
コンパイルして実行すると次のようになります。
kotlinc mistake1_sequential_async.kt -include-runtime -d mistake1_sequential_async.jar java -jar mistake1_sequential_async.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) 結果A, 結果B
async を先に起動してから await() をまとめて呼ぶと並列実行になる。
fix1_parallel_async.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred1 = async { delay(300); "並列A" }
val deferred2 = async { delay(300); "並列B" }
println("${deferred1.await()}, ${deferred2.await()}") // 約300ms で完了
}
コンパイルして実行すると次のようになります。
kotlinc fix1_parallel_async.kt -include-runtime -d fix1_parallel_async.jar java -jar fix1_parallel_async.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) 並列A, 並列B
NG例2: launch の戻り値(Job)を無視すると join() もキャンセルもできない。
mistake2_ignored_job.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { delay(100); println("完了(join なし)") }
// join() を呼ばないと runBlocking が先に終わる場合がある
}
job を保持して join() で完了を待つ。
fix2_join_job.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch { delay(100); println("完了(join あり)") }
job.join()
}
コンパイルして実行すると次のようになります。
kotlinc fix2_join_job.kt -include-runtime -d fix2_join_job.jar java -jar fix2_join_job.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) 完了(join あり)
NG例3: launch 内で発生した例外を外側の try-catch ではキャッチできない。
mistake3_launch_exception.kt
import kotlinx.coroutines.*
// try {
// launch { throw RuntimeException("launch 内のエラー") }
// } catch (e: Exception) {
// println("キャッチ: ${e.message}") // これはキャッチされない
// }
async + await() を使えば呼び出し元で例外をキャッチできる。
fix3_async_exception.kt
import kotlinx.coroutines.*
fun main() = runBlocking {
val failing = async { throw RuntimeException("async 内のエラー") }
try {
failing.await()
} catch (e: RuntimeException) {
println("キャッチ: ${e.message}")
}
}
コンパイルして実行すると次のようになります。
kotlinc fix3_async_exception.kt -include-runtime -d fix3_async_exception.jar java -jar fix3_async_exception.jar # ※ kotlinx-coroutines-core ライブラリが必要です(kotlinc 単体では実行不可) キャッチ: async 内のエラー
概要
『launch』と『async』の使い分けは戻り値が必要かどうかで決まります。結果が不要な場合は『launch』、結果を取得したい場合は『async』と『await()』を使います。
複数の『async』を並行して起動し、後でまとめて『await()』を呼ぶパターンが一般的です。順番に呼ぶと順次実行になりますが、起動してから後でまとめて待つことで並列実行になります。
中断可能な関数の定義はsuspend 関数を、待機とタイムアウトはdelay() / withTimeout()を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。