Contents
Coroutines の基礎概念
| 用語 | 説明 |
|---|---|
| suspend 関数 | コルーチン内部でのみ呼び出せる「中断可能」関数。delay や withContext など、実行を一時停止して他のタスクへ制御を譲れる。 |
| CoroutineScope | ライフサイクル(キャンセルや例外伝搬)を管理するコンテナ。スコープが終了するとその中で起動したすべての子コルーチンもキャンセルされる。 |
| Dispatcher | コルーチンが実行されるスレッドプールを指定するオブジェクト。代表的なものは Dispatchers.Default(CPU バウンド)、Dispatchers.IO(I/O バウンド)、Dispatchers.Main(UI スレッド)です。 |
| launch / async | コルーチンの開始方法。launch は結果を返さない fire‑and‑forget、async は Deferred<T> を通じて結果取得が可能。 |
参考: Kotlin公式ドキュメント「Coroutines Overview」https://kotlinlang.org/docs/coroutines-overview.html
suspend 関数のシンプルな例
|
1 2 3 4 5 6 7 |
import kotlinx.coroutines.delay suspend fun fetchMessage(): String { delay(1000) // スレッドはブロックされないまま 1 秒待機 return "Hello from Coroutine" } |
この関数は 中断可能 であるため、withContext, launch, async などから呼び出すと非同期処理が直線的なコードで記述できる。
開発環境のセットアップ
Gradle (Kotlin DSL) 推奨設定
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
plugins { kotlin("jvm") version "1.9.0" } repositories { mavenCentral() } // バージョンは「最新」またはプロジェクトで管理している BOM を使用 val coroutinesVersion = "1.8.0" // 例:2024年時点の最新版 dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") // Android プロジェクトの場合は下記も追加 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion") } |
- バージョン固定の回避:
gradle.propertiesや version catalog で変数化すれば、将来的に依存関係を一括更新できる。 - IDE 補完: Android Studio の Kotlin プラグインは自動的に Coroutines 用コード補完とドキュメントリンクを提供します(プラグインは最新バージョンを使用してください)。
参考: 「Getting Started with Kotlin Coroutines」https://kotlinlang.org/docs/coroutine-getting-started.html
基本 API と実装例
launch と async の使い分け
| 関数 | 戻り値 | 主な利用シーン |
|---|---|---|
launch |
Job |
UI 更新のみが目的で結果を受け取らない場合(fire‑and‑forget) |
async |
Deferred<T> |
計算結果やネットワーク応答など、後から取得したいデータがあるとき |
実装例(Android Fragment 内)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class SampleFragment : Fragment(R.layout.fragment_sample) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // UI スレッドで結果を受け取るパターン viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { val message = withContext(Dispatchers.IO) { fetchMessage() } findViewById<TextView>(R.id.textView).text = message } // fire‑and‑forget の例(重い計算をバックグラウンドで実行) viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { heavyComputation() } // async で結果取得 → UI に反映 val deferred = viewLifecycleOwner.lifecycleScope.async(Dispatchers.IO) { computeValue() } viewLifecycleOwner.lifecycleScope.launch { val result = deferred.await() Log.d("Sample", "Result = $result") } } } |
withContextは スレッド切替 に利用し、結果は呼び出し元のコンテキスト(ここではDispatchers.Main)に戻ります。lifecycleScopeが自動的に Fragment のライフサイクルと連携するため、手動でキャンセルを書く必要がありません。
Structured Concurrency とエラーハンドリング
CoroutineScope と SupervisorJob の役割
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) appScope.launch { // 子コルーチン A(正常終了) launch { delay(500); println("A completed") } // 子コルーチン B(例外発生) launch { delay(300) throw IllegalStateException("B failed") } } |
SupervisorJobを付与したスコープは、子コルーチンの失敗が兄弟に波及しない ことを保証します。- 失敗した子はキャンセルされますが、同一スコープ内の他の子は継続します。
try / catch と CancellationException の組み合わせ
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
suspend fun fetchWithTimeout(): String = withTimeout(1000L) { try { delay(2000) // 故意に長い遅延 → タイムアウトが発生 "Success" } catch (e: CancellationException) { // コルーチンがキャンセルされたときだけ捕まえる println("Operation cancelled: ${e.message}") throw e // 必要なら再スローして上位へ伝搬 } catch (e: Exception) { println("Other error: $e") "Fallback" } } |
呼び出し側で TimeoutCancellationException(CancellationException のサブクラス)を捕まえると、タイムアウト時の処理を明示的に分離できる。
|
1 2 3 4 5 6 7 8 9 |
runBlocking { try { val result = fetchWithTimeout() println(result) } catch (e: TimeoutCancellationException) { println("Timed out") } } |
参考: 「Structured concurrency」https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#structured-concurrency
Android UI での安全な利用パターン
lifecycleScope と repeatOnLifecycle の組み合わせ
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class FeedFragment : Fragment(R.layout.fragment_feed) { private val viewModel: FeedViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { // STARTED 以上の状態でだけデータ取得を実行 repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.feed.collectLatest { list -> adapter.submitList(list) } } } } } |
repeatOnLifecycleは ライフサイクルが対象ステートに入ったときだけコレクションを開始、抜けたら自動的にキャンセルするのでメモリリークの危険が低減します。viewModelScopeと組み合わせることで、UI コンポーネントからロジックを完全に分離できます。
ViewModel での Coroutines 使用例
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class FeedViewModel : ViewModel() { private val _feed = MutableStateFlow<List<Post>>(emptyList()) val feed: StateFlow<List<Post>> = _feed.asStateFlow() init { viewModelScope.launch(Dispatchers.IO) { repository.getPosts() .collect { posts -> _feed.value = posts } } } } |
viewModelScopeは ViewModel が破棄された瞬間に自動キャンセル されるため、Activity/Fragment のライフサイクルと独立した安全なバックグラウンド処理が実装できる。
参考: Android Developers 「Coroutines on Android」https://developer.android.com/kotlin/coroutines
Flow によるリアクティブストリーム
基本的な Flow の作り方と演算子の組み合わせ
|
1 2 3 4 5 6 7 |
fun search(query: String): Flow<List<Result>> = flow { emit(fetchFromNetwork(query)) // ネットワーク結果を最初に流す delay(500) // デバウンス用の待機 emit(fetchFromCache(query)) // キャッシュ結果で上書き }.flowOn(Dispatchers.IO) .distinctUntilChanged() |
flowは cold ストリームなので、collectが呼ばれた瞬間に実行が開始されます。flowOnで I/O スレッドへ処理を移し、UI スレッドのブロックを防止します。
UI 側での利用例(SearchView)
|
1 2 3 4 5 6 7 8 9 |
lifecycleScope.launch { searchEditText.textChanges() // extension function from androidx.core .debounce(300) // 300ms の入力停止を待つ .flatMapLatest { query -> repository.search(query) } .collectLatest { results -> adapter.submitList(results) } } |
debounceとflatMapLatestにより、古い検索リクエストがキャンセル され、最新の入力だけが処理対象になる。
参考: 「Kotlin Flow」https://kotlinlang.org/docs/flow.html
テスト戦略とツール
| ツール | 用途 |
|---|---|
runBlockingTest(kotlinx-coroutines-test) |
ディスパッチャーをテスト専用に置き換えて、時間制御や delay を即時進行させる。 |
Turbine (app.cash.turbine) |
Flow の emit/completion をシンプルに検証できる DSL。 |
| MockK / Mockito Kotlin | コルーチン内部で呼び出すサスペンド関数やリポジトリをモック化。 |
テスト例:Flow の期待結果を検証
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@OptIn(ExperimentalCoroutinesApi::class) class RepositoryTest { private val repository = Repository() @Test fun `search flow emits correct items`() = runTest { // 1. Flow を取得 val flow = repository.search("kotlin") // 2. Turbine で検証 flow.test { assertEquals(listOf(Result("Kotlin 1")), awaitItem()) cancelAndConsumeRemainingEvents() } } } |
runTest(runBlockingTestの後継)により 仮想時間 が提供され、delay等は手動で進められる。- Turbine の
awaitItem()は次の emit を待ち受け、テストが非同期コードに対しても決定的になる。
参考: 「Testing Coroutines」https://kotlinlang.org/docs/coroutine-test.html
パフォーマンスチューニングのベストプラクティス
- Dispatcher の適切な選択
- CPU バウンド処理 →
Dispatchers.Default(内部は ForkJoinPool) - I/O 待ちが多い処理 →
Dispatchers.IO(スレッド数を自動拡張) -
UI 更新は必ず
Dispatchers.Main -
Structured Concurrency の徹底
- アプリ全体で 1〜2 個のトップレベルスコープ(例:
applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default))を作り、そこから派生させる。 -
余計な
GlobalScopeの使用は メモリリーク・キャンセル漏れ の原因になるため避ける。 -
キャンセル伝搬の明示的管理
kotlin
val job = viewModelScope.launch {
withTimeoutOrNull(5000) { longRunningTask() } ?: cancel("タイムアウト")
} -
withTimeoutOrNullでタイムアウト時にnullを返すだけにし、必要なら手動でcancel()して上位へ伝搬させる。 -
バックプレッシャーの活用
-
Flowの演算子(buffer,conflate,collectLatest)を組み合わせ、プロデューサとコンシューマ間の速度差を吸収する。 -
テストカバレッジの指標
- Flow ロジックは最低 80% のステート遷移をテストし、
runTestと Turbine を併用して非決定的要素を排除する。
学習ロードマップ & 推奨教材
| フェーズ | 内容 | 具体的なリソース |
|---|---|---|
| 1. 基礎固め | Coroutines の概念・suspend/launchの使い方 |
Kotlin公式ドキュメント「Coroutines Overview」https://kotlinlang.org/docs/coroutines-overview.html |
| 2. 実装練習 | 簡単な Hello World、ネットワーク呼び出しシミュレーション | JetBrains のサンプルリポジトリ kotlin-coroutines-examples(GitHub) |
| 3. Android への統合 | lifecycleScope, repeatOnLifecycle, ViewModelScope の実践 |
Android Developers「Coroutines on Android」https://developer.android.com/kotlin/coroutines |
| 4. Flow とリアクティブプログラミング | Cold/Hot ストリーム、演算子の組み合わせ | 「Kotlin Flow」公式ガイド https://kotlinlang.org/docs/flow.html |
| 5. テストとデバッグ | runTest, Turbine, MockK を使ったユニットテスト |
「Testing Coroutines」https://kotlinlang.org/docs/coroutine-test.html |
| 6. パフォーマンス最適化 | Dispatcher 選択基準、バックプレッシャー、キャンセル戦略 | 書籍 Programming Kotlin(O'Reilly, 2023年版) |
| 7. 継続的学習 | 最新の API 変更やベストプラクティスを追う | Kotlin Blog https://blog.jetbrains.com/kotlin/、GitHub kotlin/kotlinx.coroutines のリリースノート |
※上記書籍は2023年に出版された実績あるものです。2026 年以降の予測的情報は除外しています。
まとめ
- Coroutines は「中断可能」な
suspend関数と Structured Concurrency によって、非同期処理を直線的かつ安全に記述できる強力なツールです。 - 正しい Dispatcher の選択と スコープ管理(
lifecycleScope,viewModelScope,SupervisorJob)が、メモリリークや例外伝搬の防止につながります。 - Flow はリアクティブストリームを自然に扱えるので、検索・ページングなどデータ更新頻度の高い UI に最適です。
- テストは
runTestと Turbine を組み合わせれば、時間依存やスレッド競合を排除した決定的な検証が可能です。 - 本ガイドで示した学習ロードマップに沿って公式ドキュメント・実務サンプル・書籍を順次消化すれば、Kotlin Coroutines を安全かつ高性能に活用できるようになります。
本稿の情報は 2024 年 11 月時点で確認された公式リソースと信頼できる出版物に基づいています。バージョン番号や API の詳細は、プロジェクトごとの依存関係管理方式(Gradle version catalog 等)を用いることで常に最新状態を保つことが推奨されます。