はじめに
―― 並行処理でアプリを高速化するコルーチンの真髄 ――
Kotlin のコルーチン(Coroutine)は、非同期処理をシンプルかつ安全に書ける仕組みです。
その中でも特に重要な構文が async / await。
これを使うと、複数の処理を並行して実行し、結果を同時に待つことができます。
1. async と await の基本
概念
-
async:非同期タスクを起動し、Deferred<T>を返す。 -
await:その結果(Deferred)を待ち、完了した値を受け取る。
両者を組み合わせることで、複数の処理を同時に進めつつ結果を集約できます。
サンプルコード
import kotlinx.coroutines.*
fun main() = runBlocking {
val apple = async {
delay(1000)
"🍎 Apple"
}
val orange = async {
delay(500)
"🍊 Orange"
}
println("結果を待っています...")
val result = "${apple.await()} & ${orange.await()}"
println("完了! -> $result")
}
出力:
結果を待っています...
完了! -> 🍎 Apple & 🍊 Orange
各タスクは同時に動作するため、全体で約1秒で終了します。
(1,000ms + 500ms ではなく、max(1000, 500))
2. async と launch の違い
| 比較項目 | async |
launch |
|---|---|---|
| 戻り値 |
Deferred<T>(値を返す) |
Job(値を返さない) |
| 用途 | 結果を受け取りたい | 処理だけ実行したい |
| 結果の取得 | .await() |
.join() |
| 使用例 | 並列計算・API呼び出し | UI更新・ログ出力など |
例:
val result = async { calcResult() }.await()
launch { saveLog() }.join()
3. async は CoroutineScope 内でのみ使用可能
// ✅ OK:コルーチンスコープ内
runBlocking {
async { println("OK") }
}
// ❌ NG:スコープ外ではエラー
async { println("NG") }
asyncはrunBlocking,coroutineScope,launchなどの中でしか使えません。
4. Dispatcher を指定してスレッドを切り替える
async は、どのスレッドで処理を行うか指定できます。
| Dispatcher | 説明 | 代表的な用途 |
|---|---|---|
Dispatchers.Default |
CPU負荷の高い計算用 | 数値計算、並列処理 |
Dispatchers.IO |
I/O最適化 | ネットワーク、DB、ファイル |
Dispatchers.Main |
メインスレッド | UI更新(Android) |
val data = async(Dispatchers.IO) {
fetchFromNetwork()
}.await()
5. 複数APIを並行して呼び出す実例
suspend fun fetchUser(): String {
delay(1000)
return "UserData"
}
suspend fun fetchPosts(): String {
delay(800)
return "PostData"
}
fun main() = runBlocking {
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts() }
println("両方の結果を待機中...")
val result = "${userDeferred.await()} + ${postsDeferred.await()}"
println("完了! -> $result")
}
実行時間:
2つの処理を並列化することで、合計 1.8秒 → 約1秒 に短縮。
6. start = CoroutineStart.LAZY で遅延起動
通常、async は呼び出し時点で即実行されます。
必要になってから動かしたい場合は、LAZY 起動を使います。
val deferred = async(start = CoroutineStart.LAZY) {
heavyTask()
}
println("まだ開始していません…")
deferred.start() // 手動で開始
val result = deferred.await()
7. エラー処理(try-catch)
await() は例外をスローします。
そのため try-catch で安全に包むのがベストです。
try {
val data = async { apiCall() }.await()
println("取得成功: $data")
} catch (e: Exception) {
println("エラー発生: ${e.message}")
}
8. 構造化並行性(Structured Concurrency)
Kotlin の async/await は 構造化並行性 に従います。
つまり、親スコープが終了すると、子コルーチンも自動的にキャンセルされます。
coroutineScope {
val a = async { taskA() }
val b = async { taskB() }
// スコープ終了時に a,b も終了
}
これにより、リークしない安全な並行処理が実現します。
9. async と withContext の違い
| 比較項目 | async |
withContext |
|---|---|---|
| 並行実行 | 可能 | 不可(順次実行) |
| 戻り値 |
Deferred<T>(後で受け取る) |
直接 T を返す |
| 使い分け | 並列計算・複数API呼び出し | 単一処理でスレッド切替したい時 |
例:
val user = withContext(Dispatchers.IO) { fetchUser() } // 順次実行
val posts = withContext(Dispatchers.IO) { fetchPosts() } // 順次実行
並列化したい場合は
asyncを選びましょう。
まとめ
| 概要 | 内容 |
|---|---|
async |
非同期タスクを起動し、Deferred<T>を返す |
await |
タスクの完了を待ち、結果を取得する |
| メリット | 複数の処理を並行して実行できる |
| 典型利用例 | 複数API呼び出し、並列計算、バッチ処理 |
| 注意点 | スコープ内で使用、例外処理を忘れずに |
-
async / awaitは Kotlin コルーチンの非同期並行処理の中核 - I/O と計算を分離し、効率的にタスクをまとめて処理できる
- 構造化並行性により、メモリリークのない安全な非同期処理が可能