CoroutineScope — 概要、ライフサイクル、コルーチンでの動作

著者: IT Sectr 公開日: 2026-06-22 読了時間: 10 分

CoroutineScopeはKotlinのインターフェースで、コルーチンのライフサイクルを定義し、新しいコルーチンを起動するためのコンテキストを提供します。Kotlinのドキュメント(2025年)によると、各CoroutineScopeインスタンスはCoroutineContextを含み、その中で起動されたすべてのコルーチンを管理します。スコープがキャンセルされると、すべての子コルーチンが自動的にキャンセルされ、メモリリークを防ぎます。

重要なポイント

  • CoroutineScope — 単一のCoroutineContextフィールドを持つインターフェースで、コルーチンのライフサイクルを定義します
  • Job — キャンセルを担当するコンテキスト要素:スコープのキャンセルですべての子コルーチンがキャンセルされます
  • 構造化された並行処理 — 子コルーチンが親スコープにバインドされる原則
  • GlobalScope — アプリケーション全体のスコープで、メモリリークのリスクがあるため推奨されません
  • supervisorScope — 1つの子コルーチンをキャンセルしても他はキャンセルされない特別なスコープ

KotlinのCoroutineScopeとは?

CoroutineScopeはkotlinx.coroutinesライブラリの基本的なインターフェースで、コルーチンのコンテナとして機能します。コルーチンのライフサイクルの境界を定義します:スコープが完了すると、その中のすべてのコルーチンが自動的にキャンセルされます。

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

インターフェースには1つのフィールドしかありません — coroutineContextです。これを介して、スコープはその中で起動されたすべてのコルーチンにディスパッチャ(Dispatcher)、ジョブ(Job)、例外ハンドラ、その他のコンテキスト要素を提供します。

kotlinx.coroutinesライブラリでの役割

すべてのコルーチン起動関数 — launchasyncrunBlocking — はCoroutineScopeの拡張関数です。つまり、スコープオブジェクトが利用可能な場合にのみ呼び出すことができます。この設計により、各コルーチンに明確に定義された親とライフサイクルが保証されます。

CoroutineScopeの使用場所

Androidでは、各アーキテクチャコンポーネントに独自のスコープがあります:ViewModel用のviewModelScope、Activity/Fragment用のlifecycleScope。サーバーアプリケーションでは、スコープをHTTPリクエストやデータベース接続プールにバインドできます。

CoroutineScopeの仕組み:Jobと構造化された並行処理

CoroutineScopeの内部動作を理解するには、Jobの概念と構造化された並行処理の原則に精通する必要があります。

Job — コルーチンのタスク

各コルーチンは起動時にJobオブジェクト(asyncの場合はDeferred)を返します。Jobは有限のライフサイクルを持つタスクを表します:New、Active、Completing、Completed、Cancelling、Cancelled。Jobオブジェクトはツリー構造を形成します:

  • 親Job — コルーチンが起動されたスコープ
  • 子Job — launch/asyncを介して起動された各コルーチン
  • 親のキャンセル → すべての子のキャンセル
  • 子での例外 → 親のキャンセル(supervisorScopeを除く)

構造化された並行処理の原則

構造化された並行処理はKotlin Coroutinesの主要なアーキテクチャ原則であり、コルーチンのライフサイクルがそのスコープのライフサイクルにバインドされます。これは“ファイア・アンド・フォーゲット”モデルとは対照的で、後者ではスコープ完了後もコルーチンが生存し続けます。構造化された並行処理の利点:

  • 予測可能なライフサイクル — スコープが完了すると、すべてのコルーチンが確実に停止されます
  • 自動エラー処理 — 子コルーチンの例外がスコープに伝播します
  • メモリリークなし — スコープ完了後にコルーチンが実行され続けることはありません
  • 明確な階層構造 — コードが並列操作の論理構造を反映します

CoroutineScopeのライフサイクル

scope.cancel()が呼び出されると、スコープのJobはCancelled状態に移行し、すべての子Jobを再帰的にキャンセルします。キャンセル後、新しいCoroutineScopeインスタンスを作成しない限り、スコープを再利用することはできません。

CoroutineScopeの作成と設定

CoroutineScopeはファクトリ関数を使用するか、クラスにインターフェースを実装して作成できます。両方のアプローチを見てみましょう。

ファクトリ関数 CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Running on ${Thread.currentThread().name}")
}

ファクトリ関数はCoroutineContextを受け取り、指定されたコンテキストでスコープを作成します。この例では、CPU集約型タスクにDispatchers.Defaultを使用し、子コルーチン間で例外を分離するSupervisorJobを使用しています。

コンポジションによるインターフェースの実装

kotlin
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    suspend fun fetchData(): Data = scope.async {
        api.getData()
    }.await()

    fun cleanup() {
        scope.cancel()
    }
}

スコープをクラスフィールドとして保存し、手動でcleanupを呼び出してキャンセルします。このアプローチは、ライフサイクルが管理されたコンポーネント(リポジトリやマネージャーなど)に適しています。

委譲による実装

Kotlinでは、byキーワードを使用してCoroutineScopeの実装を委譲できます:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // coroutine runs in DataLoader scope
        }
    }
}

このアプローチは、クラス自体がスコープであり、コルーチン起動メソッドを提供したい場合に便利です。ただし注意してください:クラスはCoroutineScopeのすべてのメソッド(cancelを含む)を継承するため、カプセル化が壊れる可能性があります。

GlobalScope vs カスタムCoroutineScope

GlobalScopeはアプリケーション全体のシングルトンCoroutineScopeです。プロダクションコードでの使用は公式に推奨されていません。

GlobalScopeの問題点

  • 構造化された並行処理の欠如 — GlobalScopeのコルーチンはコンポーネントのライフサイクルにバインドされません
  • メモリリーク — Activity/Fragmentが閉じられた後もコルーチンが実行され続ける可能性があります
  • テストの困難さ — GlobalScopeはテストで置き換えられません
  • リソース消費の制御不能 — 多くのコルーチンが予想より長く実行される可能性があります

GlobalScopeが正当化される場合

JetBrainsは、まれなシナリオでのみGlobalScopeを許可しています:すべてのActivityが閉じられた後も存続すべきアプリケーションレベルのバックグラウンドプロセス(データ同期、分析など)。しかし、これらの場合でも、CoroutineScope(SupervisorJob())で独自のスコープを作成することが推奨されます。

推奨事項

明示的なライフサイクル管理を持つカスタムCoroutineScopeを常に使用してください。Androidでは、これらはviewModelScopeとlifecycleScopeです。サーバーアプリケーションでは、リクエストまたはコネクションプールごとにスコープを作成します。

coroutineScope vs supervisorScope:違いは何か

両方の関数はサスペンド関数であり、並列タスク用の一時的なスコープを作成しますが、例外に対する動作が根本的に異なります。

特性coroutineScopesupervisorScope
エラー時の動作子コルーチンの例外が他すべてをキャンセル子コルーチンの例外は他をキャンセルしない
エラーの伝播はい、最初の例外が外部に伝播しますはい、最初の例外が外部に伝播します
デフォルトのJobJob() — 子は親にバインドSupervisorJob() — 子は互いに独立
典型的なユースケース複数ステップのアトミック操作独立した並列タスク(UI読み込み)

coroutineScopeを選ぶべき場合

複数の並列操作が単一のアトミック操作を形成する場合はcoroutineScopeを使用します。例えば、3つのサーバーからデータを読み込む場合:1つのリクエストが失敗すれば、残りは意味がありません。

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

getProductまたはgetReviewsが例外をスローすると — 両方のコルーチンがキャンセルされ、例外が呼び出し元のコードに伝播します。

supervisorScopeを選ぶべき場合

並列操作が互いに独立している場合はsupervisorScopeを使用します。例えば、複数の独立したセクションでプロファイルデータを読み込む場合:レコメンデーションセクションが失敗しても、プロファイルヘッダーと友達リストは表示されるべきです。

CoroutineScope使用時のよくある間違い

KotlinでCoroutineScopeを使用する際の開発者の最も一般的な間違いを見てみましょう。

間違い1:スコープのキャンセルを忘れる

コルーチンリークの最も一般的なシナリオは、コンポーネント終了時にcancelを呼び出さずにスコープを作成することです。スコープがキャンセルされないと、コルーチンは実行を続け、オブジェクトへの参照を保持します。Androidでは、自動的にキャンセルされるviewModelScopeまたはlifecycleScopeを使用してください。

間違い2:ActivityやFragmentでGlobalScopeを使用する

GlobalScopeはAndroidコンポーネントのライフサイクルを無視します。Activityが閉じられた後にGlobalScopeで起動されたコルーチンは実行を続け、UIを更新しようとします — これによりクラッシュが発生します。UIコンポーネントには常にlifecycleScopeを使用してください。

間違い3:キャンセルされたスコープの再利用

cancel()を呼び出した後、スコープは再利用できません — その中のすべてのコルーチンは既に完了しています。ファクトリ関数を介して新しいCoroutineScopeインスタンスを作成してください。Job()は再アクティブ化をサポートしていません。

間違い4:CoroutineScopeインターフェースの誤った委譲

byで委譲すると、クラスはどこからでも呼び出せるパブリックなcancel()メソッドを取得し、カプセル化を壊します。インターフェースを委譲する代わりに、スコープをプライベートフィールドとして保存してください。

よくある質問

CoroutineScopeとCoroutineContextの違いは何ですか?

CoroutineScopeはCoroutineContextを所有し、コルーチンのライフサイクルを管理するインターフェースです。CoroutineContextは、コルーチンの“実行方法”を定義する要素(ディスパッチャ、job、エラーハンドラ)のセットです。違いの1つ:スコープはコルーチンを作成し、コンテキストはその動作を制御します。

SupervisorJobでCoroutineScopeを作成できますか?

はい、これは標準的なパターンです:CoroutineScope(Dispatchers.IO + SupervisorJob())。SupervisorJobは、1つの子コルーチンで例外が発生した場合に、他の子コルーチンの連鎖的なキャンセルを防ぎます。これは、1つのエラーが他を止めるべきではない独立した並列タスクに役立ちます。

CoroutineScopeにはいくつのコルーチンを含めることができますか?

スコープ内のコルーチン数に制限はありません — 利用可能なメモリとディスパッチャの設定によってのみ制限されます。実用的な制限は通常、1つのスコープで数千のアクティブなコルーチンです。ただし、多数のコルーチンはアーキテクチャ上の問題を示している可能性があります。

CoroutineScopeを使用するコードをテストするには?

正しい方法は、コンストラクタを介してクラスにスコープを渡すか、kotlinx-coroutines-testのrunBlockingTest / runTestを使用することです。テストでは、スコープをTestCoroutineDispatcherに置き換え、コルーチンの実行を手動で制御できます。

コルーチンは独自のスコープを持つことができますか?

いいえ、スコープはコルーチンの外部コンテナです。コルーチン自体はスコープではありません。ただし、コルーチン内でcoroutineScopeまたはsupervisorScopeを介して新しいスコープを作成し、子コルーチンを並列に起動することはできます。

まとめ

  • CoroutineScope — coroutineContextフィールドを持つインターフェースで、その中で起動されたコルーチンのライフサイクルを定義します
  • 構造化された並行処理 — スコープをキャンセルするとすべての子コルーチンが自動的にキャンセルされ、メモリリークを防ぎます
  • JobとSupervisorJob — 2つのエラー処理モード:連鎖的キャンセル(Job)と分離されたエラー(SupervisorJob)
  • GlobalScope — ライフサイクルバインディングの欠如によりプロダクションには非推奨
  • coroutineScope vs supervisorScope — アトミック並列操作 vs 独立した並列タスク
  • viewModelScopeとlifecycleScope — Android用の既成スコープ、コンポーネント終了時に自動的にキャンセル
  • ファクトリ関数 — CoroutineContext + 明示的なcancel呼び出しによるスコープ作成の推奨方法

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください