はじめに
Kotlin のコルーチンでは、非同期処理を簡潔に書ける3つの主要関数があります:
| 関数 | 戻り値 | 用途 | 結果の取得方法 |
|---|---|---|---|
launch |
Job |
戻り値が不要な処理 | .join() |
async |
Deferred<T> |
並列に計算・取得したい処理 | .await() |
withContext |
T |
スレッドを切り替えて順次実行 | 戻り値そのもの |
それぞれ似ていますが、
「並行実行するか」 と 「結果をどう扱うか」 が大きく違います。
1. launch ― 戻り値不要な非同期処理
launch は 「裏で動かしたいけど結果はいらない」 タスクを起動する。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(1000)
println("🌙 Launch Task Done!")
}
println("🌞 Main continues...")
job.join() // 完了を待つ
}
ポイント:
- 戻り値は
Job -
.join()で待機できる - 戻り値が不要な処理(UI更新・ログ送信など)に最適
2. async / await ― 結果を返す並行処理
async は非同期で計算を開始し、結果を Deferred として返す。
await() でその結果を受け取る。
fun main() = runBlocking {
val apple = async {
delay(1000)
"🍎 Apple"
}
val orange = async {
delay(500)
"🍊 Orange"
}
val result = "${apple.await()} & ${orange.await()}"
println("完了 → $result")
}
ポイント:
- 戻り値は
Deferred<T> -
.await()で結果を取得 - 並列に実行可能
- 戻り値が必要な処理(APIリクエスト・計算処理)に最適
3. withContext ― スレッド切り替えで順次処理
withContext は スレッド(Dispatcher)を切り替えて処理を実行し、その結果を返す。
他の処理と並行実行はしない。
fun main() = runBlocking {
val data = withContext(Dispatchers.IO) {
delay(1000)
" Loaded data"
}
println("結果: $data")
}
ポイント:
- スレッドを安全に切り替える(例:
Main⇔IO) - 戻り値を直接返す(
T) - 順次処理(直列)
- 典型的には:UI→IO→UI の切り替え
4. 機能比較まとめ
| 比較項目 | launch |
async / await |
withContext |
|---|---|---|---|
| 戻り値 | Job |
Deferred<T> |
T |
| 結果の取得 | .join() |
.await() |
戻り値そのもの |
| 並列実行 | 可能 | 可能 | 不可(順次) |
| 用途 | ログ・UI更新など | 並列計算、複数API呼び出し | スレッド切替、IO操作 |
| Dispatcher指定 | 可 | 可 | 可 |
| 例外処理 | try-catch で制御 | try-catch で制御 | try-catch で制御 |
| スコープ |
CoroutineScope 内 |
CoroutineScope 内 |
suspend 関数内 |
5. 実践比較:API呼び出し3パターン
(A) launch:結果不要な非同期
launch {
apiCall() // 戻り値を使わない
println("送信完了")
}
(B) async / await:結果を並列取得
val user = async { fetchUser() }
val posts = async { fetchPosts() }
val result = "${user.await()} + ${posts.await()}"
println(result)
(C) withContext:順次スレッド切り替え
val user = withContext(Dispatchers.IO) { fetchUser() }
val posts = withContext(Dispatchers.IO) { fetchPosts() }
println("結果: $user + $posts")
6. 図解:それぞれの動作の違い
7. 使い分けの指針
| シーン | 最適な選択 | 理由 |
|---|---|---|
| 戻り値が不要な非同期処理 | launch |
軽量で Job 管理が容易 |
| 複数APIを並列で取得 | async / await |
並行処理ができる |
| スレッドを切り替えて実行 | withContext |
シンプルで安全 |
| UI → IO → UI 切替 | withContext |
Main ↔ IO の切替が簡単 |
| バッチ処理・データ集約 |
async + awaitAll()
|
効率的に結果をまとめる |
8. 例:3つの構文を組み合わせた実践コード
suspend fun fetchUser() = withContext(Dispatchers.IO) {
delay(1000)
" User"
}
suspend fun fetchPosts() = withContext(Dispatchers.IO) {
delay(800)
" Posts"
}
fun main() = runBlocking {
launch {
println("データ取得開始")
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts() }
val result = "${userDeferred.await()} + ${postsDeferred.await()}"
println("結果: $result")
println("完了しました")
}
}
まとめ
| 概念 | launch | async / await | withContext |
|---|---|---|---|
| 戻り値 | Job | Deferred | T |
| 並行性 | あり | あり | なし(順次) |
| スレッド切替 | 可 | 可 | 可 |
| 典型用途 | 結果不要処理 | 並列計算・API集約 | IO処理・UI更新 |
| 例外処理 | try-catch | try-catch | try-catch |
| 構造化並行性 | ✅ | ✅ | ✅ |
- launch:結果不要の非同期処理に最適
- async / await:並行実行+結果が必要な処理に最適
- withContext:スレッドを安全に切り替えるための順次処理に最適
3つを正しく使い分けることで、
Kotlin アプリの 非同期処理は安全・高速・美しく なります。