CoroutineScopeはKotlinのインターフェースで、コルーチンのライフサイクルを定義し、新しいコルーチンを起動するためのコンテキストを提供します。Kotlinのドキュメント(2025年)によると、各CoroutineScopeインスタンスはCoroutineContextを含み、その中で起動されたすべてのコルーチンを管理します。スコープがキャンセルされると、すべての子コルーチンが自動的にキャンセルされ、メモリリークを防ぎます。
重要なポイント
CoroutineScopeはkotlinx.coroutinesライブラリの基本的なインターフェースで、コルーチンのコンテナとして機能します。コルーチンのライフサイクルの境界を定義します:スコープが完了すると、その中のすべてのコルーチンが自動的にキャンセルされます。
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
インターフェースには1つのフィールドしかありません — coroutineContextです。これを介して、スコープはその中で起動されたすべてのコルーチンにディスパッチャ(Dispatcher)、ジョブ(Job)、例外ハンドラ、その他のコンテキスト要素を提供します。
すべてのコルーチン起動関数 — launch、async、runBlocking — はCoroutineScopeの拡張関数です。つまり、スコープオブジェクトが利用可能な場合にのみ呼び出すことができます。この設計により、各コルーチンに明確に定義された親とライフサイクルが保証されます。
Androidでは、各アーキテクチャコンポーネントに独自のスコープがあります:ViewModel用のviewModelScope、Activity/Fragment用のlifecycleScope。サーバーアプリケーションでは、スコープをHTTPリクエストやデータベース接続プールにバインドできます。
CoroutineScopeの内部動作を理解するには、Jobの概念と構造化された並行処理の原則に精通する必要があります。
各コルーチンは起動時にJobオブジェクト(asyncの場合はDeferred)を返します。Jobは有限のライフサイクルを持つタスクを表します:New、Active、Completing、Completed、Cancelling、Cancelled。Jobオブジェクトはツリー構造を形成します:
構造化された並行処理はKotlin Coroutinesの主要なアーキテクチャ原則であり、コルーチンのライフサイクルがそのスコープのライフサイクルにバインドされます。これは“ファイア・アンド・フォーゲット”モデルとは対照的で、後者ではスコープ完了後もコルーチンが生存し続けます。構造化された並行処理の利点:
scope.cancel()が呼び出されると、スコープのJobはCancelled状態に移行し、すべての子Jobを再帰的にキャンセルします。キャンセル後、新しいCoroutineScopeインスタンスを作成しない限り、スコープを再利用することはできません。
CoroutineScopeはファクトリ関数を使用するか、クラスにインターフェースを実装して作成できます。両方のアプローチを見てみましょう。
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Running on ${Thread.currentThread().name}")
}
ファクトリ関数はCoroutineContextを受け取り、指定されたコンテキストでスコープを作成します。この例では、CPU集約型タスクにDispatchers.Defaultを使用し、子コルーチン間で例外を分離するSupervisorJobを使用しています。
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の実装を委譲できます:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// coroutine runs in DataLoader scope
}
}
}
このアプローチは、クラス自体がスコープであり、コルーチン起動メソッドを提供したい場合に便利です。ただし注意してください:クラスはCoroutineScopeのすべてのメソッド(cancelを含む)を継承するため、カプセル化が壊れる可能性があります。
GlobalScopeはアプリケーション全体のシングルトンCoroutineScopeです。プロダクションコードでの使用は公式に推奨されていません。
JetBrainsは、まれなシナリオでのみGlobalScopeを許可しています:すべてのActivityが閉じられた後も存続すべきアプリケーションレベルのバックグラウンドプロセス(データ同期、分析など)。しかし、これらの場合でも、CoroutineScope(SupervisorJob())で独自のスコープを作成することが推奨されます。
明示的なライフサイクル管理を持つカスタムCoroutineScopeを常に使用してください。Androidでは、これらはviewModelScopeとlifecycleScopeです。サーバーアプリケーションでは、リクエストまたはコネクションプールごとにスコープを作成します。
両方の関数はサスペンド関数であり、並列タスク用の一時的なスコープを作成しますが、例外に対する動作が根本的に異なります。
| 特性 | coroutineScope | supervisorScope |
|---|---|---|
| エラー時の動作 | 子コルーチンの例外が他すべてをキャンセル | 子コルーチンの例外は他をキャンセルしない |
| エラーの伝播 | はい、最初の例外が外部に伝播します | はい、最初の例外が外部に伝播します |
| デフォルトのJob | Job() — 子は親にバインド | SupervisorJob() — 子は互いに独立 |
| 典型的なユースケース | 複数ステップのアトミック操作 | 独立した並列タスク(UI読み込み) |
複数の並列操作が単一のアトミック操作を形成する場合はcoroutineScopeを使用します。例えば、3つのサーバーからデータを読み込む場合:1つのリクエストが失敗すれば、残りは意味がありません。
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
getProductまたはgetReviewsが例外をスローすると — 両方のコルーチンがキャンセルされ、例外が呼び出し元のコードに伝播します。
並列操作が互いに独立している場合はsupervisorScopeを使用します。例えば、複数の独立したセクションでプロファイルデータを読み込む場合:レコメンデーションセクションが失敗しても、プロファイルヘッダーと友達リストは表示されるべきです。
KotlinでCoroutineScopeを使用する際の開発者の最も一般的な間違いを見てみましょう。
コルーチンリークの最も一般的なシナリオは、コンポーネント終了時にcancelを呼び出さずにスコープを作成することです。スコープがキャンセルされないと、コルーチンは実行を続け、オブジェクトへの参照を保持します。Androidでは、自動的にキャンセルされるviewModelScopeまたはlifecycleScopeを使用してください。
GlobalScopeはAndroidコンポーネントのライフサイクルを無視します。Activityが閉じられた後にGlobalScopeで起動されたコルーチンは実行を続け、UIを更新しようとします — これによりクラッシュが発生します。UIコンポーネントには常にlifecycleScopeを使用してください。
cancel()を呼び出した後、スコープは再利用できません — その中のすべてのコルーチンは既に完了しています。ファクトリ関数を介して新しいCoroutineScopeインスタンスを作成してください。Job()は再アクティブ化をサポートしていません。
byで委譲すると、クラスはどこからでも呼び出せるパブリックなcancel()メソッドを取得し、カプセル化を壊します。インターフェースを委譲する代わりに、スコープをプライベートフィールドとして保存してください。
よくある質問
CoroutineScopeはCoroutineContextを所有し、コルーチンのライフサイクルを管理するインターフェースです。CoroutineContextは、コルーチンの“実行方法”を定義する要素(ディスパッチャ、job、エラーハンドラ)のセットです。違いの1つ:スコープはコルーチンを作成し、コンテキストはその動作を制御します。
はい、これは標準的なパターンです:CoroutineScope(Dispatchers.IO + SupervisorJob())。SupervisorJobは、1つの子コルーチンで例外が発生した場合に、他の子コルーチンの連鎖的なキャンセルを防ぎます。これは、1つのエラーが他を止めるべきではない独立した並列タスクに役立ちます。
スコープ内のコルーチン数に制限はありません — 利用可能なメモリとディスパッチャの設定によってのみ制限されます。実用的な制限は通常、1つのスコープで数千のアクティブなコルーチンです。ただし、多数のコルーチンはアーキテクチャ上の問題を示している可能性があります。
正しい方法は、コンストラクタを介してクラスにスコープを渡すか、kotlinx-coroutines-testのrunBlockingTest / runTestを使用することです。テストでは、スコープをTestCoroutineDispatcherに置き換え、コルーチンの実行を手動で制御できます。
いいえ、スコープはコルーチンの外部コンテナです。コルーチン自体はスコープではありません。ただし、コルーチン内でcoroutineScopeまたはsupervisorScopeを介して新しいスコープを作成し、子コルーチンを並列に起動することはできます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。