KotlinのcoroutineScopeとsupervisorScopeの違いと活用例を徹底解説

Kotlinのコルーチンを活用する際、非同期処理の管理は非常に重要です。その中でも、coroutineScopesupervisorScopeは、コルーチンのライフサイクルやエラーハンドリングを効果的に管理するための重要なツールです。coroutineScopeは、スコープ内で発生するエラーが全ての子コルーチンに影響するのに対し、supervisorScopeは、エラーが発生したコルーチンのみが影響を受け、他の子コルーチンは継続して処理できます。

本記事では、これら二つのスコープの基本概念、実用例、違い、さらには適切な選択方法について詳しく解説します。これにより、Kotlinの非同期プログラミングでエラーハンドリングの柔軟性を高め、効率的なコルーチン管理ができるようになります。

目次
  1. coroutineScopeとは何か
    1. coroutineScopeの特徴
    2. 基本的な構文
    3. エラー伝播の例
  2. supervisorScopeとは何か
    1. supervisorScopeの特徴
    2. 基本的な構文
    3. エラーが分離される理由
    4. エラーハンドリングの例
  3. coroutineScopeとsupervisorScopeの違い
    1. エラーハンドリングの違い
    2. キャンセルの伝播
    3. ユースケースの違い
    4. まとめ表
    5. どちらを選ぶべきか
  4. coroutineScopeの実用例
    1. 例1:複数の非同期タスクの並行処理
    2. 例2:エラーが発生した場合の挙動
    3. 例3:依存関係のあるタスクの管理
    4. まとめ
  5. supervisorScopeの実用例
    1. 例1:複数の独立したタスクの並行処理
    2. 例2:エラーハンドリングを個別に行う
    3. 例3:非同期APIリクエストの実行
    4. まとめ
  6. どちらを選択すべきか
    1. 1. 依存関係のあるタスクの場合
    2. 2. 独立したタスクの場合
    3. 3. エラーハンドリングが重要な場合
    4. 4. 親子関係のタスク管理
    5. 選択ガイドライン
    6. 具体的なシナリオ別の選択
    7. まとめ
  7. よくある落とし穴とその回避方法
    1. 1. エラー処理の誤解
    2. 2. `supervisorScope`でエラーを放置する
    3. 3. スコープの誤ったネスト
    4. 4. 親スコープのキャンセルによる子コルーチンのキャンセル
    5. 5. 非同期処理のタイミングミス
    6. まとめ
  8. 応用例とベストプラクティス
    1. 1. ネットワークリクエストとデータベース処理の並行実行
    2. 2. 複数の独立したAPIリクエストの処理
    3. 3. UI更新とバックグラウンド処理
    4. 4. タイムアウト処理
    5. まとめ
  9. まとめ

coroutineScopeとは何か


coroutineScopeは、Kotlinのコルーチンを管理するためのスコープで、特定の処理ブロック内でコルーチンを並行して実行し、全ての子コルーチンが完了するまで待機する仕組みです。coroutineScope内でエラーが発生すると、そのエラーはスコープ全体に伝播し、スコープ内の他の子コルーチンもキャンセルされます。

coroutineScopeの特徴

  • 構造化並行処理coroutineScopeを使用することで、コルーチンの親子関係が明確になり、スコープ内で起動したコルーチンが全て完了するまでスコープが終了しません。
  • エラープロパゲーション:スコープ内でエラーが発生した場合、他の子コルーチンもキャンセルされます。
  • 同期的な振る舞いcoroutineScopeブロック内の全てのコルーチンが終了するまで、次の処理には進みません。

基本的な構文

import kotlinx.coroutines.*

fun main() = runBlocking {
    coroutineScope {
        launch {
            delay(1000L)
            println("Task 1 completed")
        }
        launch {
            delay(500L)
            println("Task 2 completed")
        }
    }
    println("All tasks completed")
}

コードの説明

  1. coroutineScope内で2つのlaunchブロックが並行して実行されます。
  2. 両方のlaunchブロックが完了するまでcoroutineScopeは終了しません。
  3. すべてのタスクが完了した後、「All tasks completed」と表示されます。

エラー伝播の例

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        coroutineScope {
            launch {
                delay(1000L)
                throw Exception("Error in Task 1")
            }
            launch {
                delay(2000L)
                println("Task 2 completed")
            }
        }
    } catch (e: Exception) {
        println("Caught exception: ${e.message}")
    }
}

エラーの説明

  • 1つ目のコルーチンでエラーが発生すると、2つ目のコルーチンもキャンセルされます。
  • coroutineScopeがエラーを検出し、try-catchブロックで例外をキャッチします。

coroutineScopeは、エラーが発生した場合に全体の処理を安全に中断するため、依存関係のある処理を行う際に有用です。

supervisorScopeとは何か


supervisorScopeは、Kotlinにおけるコルーチンのスコープの一つで、エラーハンドリングの柔軟性が高い点が特徴です。coroutineScopeと異なり、supervisorScope内で1つの子コルーチンがエラーを起こしても、他の子コルーチンには影響を与えず、それぞれの処理が独立して継続します。これにより、エラー耐性の高い並行処理が可能になります。

supervisorScopeの特徴

  • エラーの分離:1つの子コルーチンが例外をスローしても、他の子コルーチンはキャンセルされずに処理を続行できます。
  • 個別のエラーハンドリング:各子コルーチンが独自にエラー処理を行うことが可能です。
  • 安定した並行処理:複数の独立したタスクを並行して実行する場合に適しています。

基本的な構文

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        launch {
            delay(1000L)
            throw Exception("Error in Task 1")
        }
        launch {
            delay(2000L)
            println("Task 2 completed")
        }
    }
    println("All tasks in supervisorScope completed")
}

コードの説明

  1. supervisorScope内で2つのlaunchブロックが実行されます。
  2. 1つ目のlaunchでエラーが発生しても、2つ目のlaunchはキャンセルされず、処理が完了します。
  3. 最後に「All tasks in supervisorScope completed」と表示されます。

エラーが分離される理由


supervisorScopeでは、子コルーチンが独立しているため、1つの子コルーチンでエラーが発生しても、他の子コルーチンには影響を与えません。そのため、エラーが起きやすい処理や独立したタスクを並行して実行する場合に適しています。

エラーハンドリングの例

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        val job1 = launch {
            try {
                delay(1000L)
                throw Exception("Error in Task 1")
            } catch (e: Exception) {
                println("Caught exception in Task 1: ${e.message}")
            }
        }

        val job2 = launch {
            delay(2000L)
            println("Task 2 completed successfully")
        }

        job1.join()
        job2.join()
    }
    println("All tasks in supervisorScope completed")
}

エラーハンドリングの説明

  • 1つ目のコルーチンでエラーが発生しても、catchブロックで例外を処理しています。
  • 2つ目のコルーチンは問題なく処理が続行され、「Task 2 completed successfully」と表示されます。

supervisorScopeは、エラー耐性が求められる処理や、独立性が高いタスクを並行して実行する際に有用です。

coroutineScopeとsupervisorScopeの違い


KotlinにおけるcoroutineScopesupervisorScopeは、どちらもコルーチンのスコープを管理するための仕組みですが、エラーハンドリングと子コルーチンのキャンセルに関して異なる挙動を示します。以下で、両者の違いについて詳しく解説します。

エラーハンドリングの違い

  • coroutineScope
    coroutineScope内で1つの子コルーチンが例外をスローすると、スコープ内の他の子コルーチンもキャンセルされます。エラーがスコープ全体に伝播するため、依存関係のあるタスクを扱う場合に適しています。 :
  coroutineScope {
      launch {
          delay(500L)
          throw Exception("Error in Task 1")
      }
      launch {
          delay(1000L)
          println("Task 2 completed")
      }
  }


結果: 「Error in Task 1」が発生すると、「Task 2」はキャンセルされます。

  • supervisorScope
    supervisorScopeでは、1つの子コルーチンが例外をスローしても、他の子コルーチンは影響を受けずに処理を続行します。エラーが分離されるため、独立したタスクを並行して処理する場合に適しています。 :
  supervisorScope {
      launch {
          delay(500L)
          throw Exception("Error in Task 1")
      }
      launch {
          delay(1000L)
          println("Task 2 completed")
      }
  }


結果: 「Task 2 completed」が表示され、1つ目のタスクのエラーは2つ目のタスクに影響を与えません。

キャンセルの伝播

  • coroutineScope:
    親スコープがキャンセルされると、全ての子コルーチンもキャンセルされます。
  • supervisorScope:
    親スコープがキャンセルされても、個々の子コルーチンが独立しているため、特定の子コルーチンのみをキャンセルすることが可能です。

ユースケースの違い

  • coroutineScopeの主なユースケース:
  • 依存関係がある複数のタスクを管理する場合
  • エラーが発生したら全体の処理を中断する必要がある場合
  • supervisorScopeの主なユースケース:
  • 独立したタスクを並行して実行する場合
  • 1つのタスクが失敗しても他のタスクを継続させたい場合

まとめ表

特性coroutineScopesupervisorScope
エラー処理エラーがスコープ全体に伝播エラーは発生したタスクのみ
子コルーチンのキャンセル全ての子コルーチンがキャンセル影響を受けたタスクのみがキャンセル
適した用途依存関係のある処理独立したタスク

どちらを選ぶべきか

  • タスク間に依存関係がある場合や、エラーが発生した時点で処理を中断したい場合は、coroutineScopeを選択します。
  • 独立したタスクを並行して処理したい場合や、エラーが発生しても他のタスクを継続させたい場合は、supervisorScopeを選択します。

coroutineScopeの実用例


coroutineScopeは、特定のブロック内で複数のコルーチンを並行して実行し、すべての子コルーチンが完了するまで待機するためのスコープです。エラーが発生すると、スコープ内の全ての子コルーチンがキャンセルされるため、依存関係のある処理に適しています。

以下では、coroutineScopeを用いた具体的な実用例を紹介します。

例1:複数の非同期タスクの並行処理

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Starting tasks...")

    coroutineScope {
        launch {
            delay(1000L)
            println("Task 1 completed")
        }
        launch {
            delay(500L)
            println("Task 2 completed")
        }
    }

    println("All tasks completed")
}

コードの解説

  1. runBlockingでメイン関数をブロックし、非同期処理が完了するまで待機します。
  2. coroutineScope内で2つのlaunchブロックを並行して実行します。
  • 1つ目のlaunchは1秒後に「Task 1 completed」を出力。
  • 2つ目のlaunchは0.5秒後に「Task 2 completed」を出力。
  1. 全ての子コルーチンが完了するまでcoroutineScopeは終了しません。
  2. 「All tasks completed」が最後に出力されます。

出力結果:

Starting tasks...  
Task 2 completed  
Task 1 completed  
All tasks completed  

例2:エラーが発生した場合の挙動

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        coroutineScope {
            launch {
                delay(500L)
                println("Task 1 completed")
            }
            launch {
                delay(1000L)
                throw Exception("Error in Task 2")
            }
        }
    } catch (e: Exception) {
        println("Caught exception: ${e.message}")
    }
}

コードの解説

  1. coroutineScope内で2つのlaunchブロックを実行しています。
  2. 2つ目のlaunchでエラーが発生し、例外がスローされます。
  3. 1つ目のlaunchは完了せずにキャンセルされます。
  4. catchブロックで例外を処理します。

出力結果:

Caught exception: Error in Task 2  

例3:依存関係のあるタスクの管理

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Starting dependent tasks...")

    coroutineScope {
        val result1 = async {
            delay(1000L)
            println("Fetching data from API...")
            "Data from API"
        }

        val result2 = async {
            delay(500L)
            println("Processing local data...")
            "Local data processed"
        }

        val combinedResult = result1.await() + " and " + result2.await()
        println("Combined result: $combinedResult")
    }

    println("All tasks completed")
}

コードの解説

  1. coroutineScope内で2つのasyncを実行し、それぞれ非同期で結果を返します。
  2. result1は1秒後に「Data from API」を返します。
  3. result2は0.5秒後に「Local data processed」を返します。
  4. await()で両方の結果を待ち、合成した結果を出力します。

出力結果:

Starting dependent tasks...  
Processing local data...  
Fetching data from API...  
Combined result: Data from API and Local data processed  
All tasks completed  

まとめ

  • 並行処理: coroutineScopeを使えば複数のタスクを効率よく並行処理できます。
  • エラーハンドリング: スコープ内でエラーが発生した場合、他の子コルーチンがキャンセルされます。
  • 依存関係の管理: タスク間に依存関係がある場合、coroutineScopeは一括してタスクの完了を保証するのに適しています。

supervisorScopeの実用例


supervisorScopeは、Kotlinにおいてエラー耐性が高い並行処理を実現するスコープです。1つの子コルーチンがエラーをスローしても、他の子コルーチンは影響を受けずに処理を続けます。これにより、独立性の高いタスクを同時に処理する場合に適しています。

以下で、supervisorScopeを用いた具体的な実用例を紹介します。

例1:複数の独立したタスクの並行処理

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Starting tasks with supervisorScope...")

    supervisorScope {
        launch {
            delay(1000L)
            println("Task 1 completed successfully")
        }

        launch {
            delay(500L)
            throw Exception("Error in Task 2")
        }

        launch {
            delay(1500L)
            println("Task 3 completed successfully")
        }
    }

    println("All tasks in supervisorScope completed")
}

コードの解説

  1. supervisorScope内で3つのlaunchブロックが並行して実行されます。
  2. 2つ目のlaunchでエラーが発生しますが、他のタスクには影響しません。
  3. 1つ目と3つ目のタスクはエラーに関係なく正常に完了します。

出力結果:

Starting tasks with supervisorScope...  
Task 1 completed successfully  
Task 3 completed successfully  
Exception in thread "main" java.lang.Exception: Error in Task 2  
All tasks in supervisorScope completed  

例2:エラーハンドリングを個別に行う

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Starting tasks with error handling...")

    supervisorScope {
        val job1 = launch {
            try {
                delay(1000L)
                println("Task 1 completed successfully")
            } catch (e: Exception) {
                println("Caught exception in Task 1: ${e.message}")
            }
        }

        val job2 = launch {
            try {
                delay(500L)
                throw Exception("Error in Task 2")
            } catch (e: Exception) {
                println("Caught exception in Task 2: ${e.message}")
            }
        }

        val job3 = launch {
            delay(1500L)
            println("Task 3 completed successfully")
        }

        job1.join()
        job2.join()
        job3.join()
    }

    println("All tasks in supervisorScope completed")
}

コードの解説

  1. launchブロック内でtry-catchを用いて個別にエラー処理を行っています。
  2. 2つ目のタスクでエラーが発生し、catchブロックでエラーを処理しますが、他のタスクは正常に完了します。

出力結果:

Starting tasks with error handling...  
Task 1 completed successfully  
Caught exception in Task 2: Error in Task 2  
Task 3 completed successfully  
All tasks in supervisorScope completed  

例3:非同期APIリクエストの実行

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Starting API requests with supervisorScope...")

    supervisorScope {
        launch {
            try {
                val result = fetchUserData()
                println("User data: $result")
            } catch (e: Exception) {
                println("Failed to fetch user data: ${e.message}")
            }
        }

        launch {
            try {
                val result = fetchProductData()
                println("Product data: $result")
            } catch (e: Exception) {
                println("Failed to fetch product data: ${e.message}")
            }
        }
    }

    println("All API requests completed")
}

suspend fun fetchUserData(): String {
    delay(1000L)
    throw Exception("User data not found")
}

suspend fun fetchProductData(): String {
    delay(1500L)
    return "Product data received successfully"
}

コードの解説

  1. fetchUserData関数でエラーが発生し、エラーメッセージが表示されます。
  2. fetchProductData関数は正常にデータを返します。
  3. supervisorScope内でエラーが発生しても、他のリクエストは継続します。

出力結果:

Starting API requests with supervisorScope...  
Failed to fetch user data: User data not found  
Product data: Product data received successfully  
All API requests completed  

まとめ

  • 独立したタスクの並行処理: supervisorScopeは1つのタスクが失敗しても、他のタスクを継続できます。
  • エラーハンドリング: タスクごとにエラー処理が可能で、柔軟性のある非同期処理が実現できます。
  • 実用例: APIリクエストや複数の非同期タスクを同時に処理するシーンで効果的です。

どちらを選択すべきか


KotlinのcoroutineScopesupervisorScopeは、非同期処理を管理するための強力なツールですが、用途やエラーハンドリングの要件に応じて適切に使い分ける必要があります。ここでは、それぞれの選択基準を解説します。

1. 依存関係のあるタスクの場合


適している: coroutineScope

タスク同士が依存しており、一つのタスクでエラーが発生したら全体の処理を中断したい場合は、coroutineScopeを選択します。

例:
データベースからのデータ取得後、そのデータを加工して保存する場合、取得段階でエラーが起きれば後続の処理も意味がないため、全ての処理をキャンセルする方が安全です。

coroutineScope {
    val data = fetchData()
    processData(data)
    saveData(data)
}

2. 独立したタスクの場合


適している: supervisorScope

タスクが互いに独立しており、一つのタスクが失敗しても他のタスクを続行したい場合は、supervisorScopeを選択します。

例:
複数のAPIからデータを並行して取得し、1つのAPIがエラーになっても他のAPIの結果を取得したい場合。

supervisorScope {
    launch { fetchUserData() }
    launch { fetchProductData() }
    launch { fetchOrderData() }
}

3. エラーハンドリングが重要な場合

  • coroutineScope: エラーが発生した場合に、スコープ内の全タスクをキャンセルする必要がある場合に適しています。
  • supervisorScope: 各タスクごとに個別のエラーハンドリングを行い、他のタスクに影響を与えたくない場合に適しています。

4. 親子関係のタスク管理

  • coroutineScope: 親タスクが子タスクをすべて管理し、親が終了する際に子タスクもすべて終了する必要がある場合に使います。
  • supervisorScope: 親タスクがエラーを個別に管理し、子タスクのエラーが他の子タスクに影響しないようにしたい場合に適しています。

選択ガイドライン

要件適切なスコープ
タスクに依存関係があるcoroutineScope
タスクが独立しているsupervisorScope
エラーが発生したら全体を中断したいcoroutineScope
エラーが発生しても続行したいsupervisorScope

具体的なシナリオ別の選択

  1. ファイルの読み書き
  • ファイルを読み込んで、その内容を処理した後に書き込む場合:coroutineScope
  1. 複数のAPIリクエスト
  • 複数のAPIからデータを取得し、一部のAPIが失敗しても残りのデータを使いたい場合:supervisorScope
  1. ユーザーインターフェースの更新
  • 画面上で複数の独立したコンテンツをロードする場合、1つが失敗しても他の部分を表示したい:supervisorScope

まとめ

  • coroutineScopeは、依存関係のあるタスクや全体の一貫性を保つ必要がある場合に選びます。
  • supervisorScopeは、独立したタスクのエラー耐性を高め、並行処理を柔軟に行いたい場合に適しています。

これらの特性を理解し、要件に応じて適切なスコープを選択することで、Kotlinの非同期処理を効率的に管理できます。

よくある落とし穴とその回避方法


KotlinのcoroutineScopesupervisorScopeを使用する際、初心者が陥りがちな間違いや問題点がいくつか存在します。ここでは、それらの落とし穴とその回避方法を解説します。

1. エラー処理の誤解

落とし穴:
coroutineScope内でエラーが発生すると、他の子コルーチンもキャンセルされることを知らずに使用し、予期せぬキャンセルが発生する。

:

coroutineScope {
    launch {
        delay(1000L)
        println("Task 1 completed")
    }
    launch {
        throw Exception("Error in Task 2")
    }
}

回避方法:
coroutineScopeを使用する場合、エラーが伝播することを考慮し、必要ならtry-catchでエラー処理を行う。

coroutineScope {
    launch {
        try {
            delay(1000L)
            println("Task 1 completed")
        } catch (e: Exception) {
            println("Caught exception in Task 1: ${e.message}")
        }
    }
    launch {
        throw Exception("Error in Task 2")
    }
}

2. `supervisorScope`でエラーを放置する

落とし穴:
supervisorScope内でエラーが発生しても、自動的には処理されないため、エラーを見逃してしまう。

:

supervisorScope {
    launch {
        delay(500L)
        throw Exception("Error in Task 1")
    }
    launch {
        delay(1000L)
        println("Task 2 completed")
    }
}

回避方法:
個別にtry-catchでエラー処理を行い、エラーを明示的に処理する。

supervisorScope {
    launch {
        try {
            delay(500L)
            throw Exception("Error in Task 1")
        } catch (e: Exception) {
            println("Caught exception in Task 1: ${e.message}")
        }
    }
    launch {
        delay(1000L)
        println("Task 2 completed")
    }
}

3. スコープの誤ったネスト

落とし穴:
coroutineScopesupervisorScopeを不必要にネストし、パフォーマンスやコードの可読性を低下させる。

:

runBlocking {
    coroutineScope {
        supervisorScope {
            coroutineScope {
                launch {
                    println("Task executed")
                }
            }
        }
    }
}

回避方法:
必要なスコープだけを使用し、スコープのネストを最小限に抑える。

runBlocking {
    launch {
        println("Task executed")
    }
}

4. 親スコープのキャンセルによる子コルーチンのキャンセル

落とし穴:
親スコープがキャンセルされると、子コルーチンもキャンセルされることを考慮せずに設計してしまう。

:

val job = GlobalScope.launch {
    coroutineScope {
        launch {
            delay(2000L)
            println("Task completed")
        }
    }
}
delay(1000L)
job.cancel() // 親スコープがキャンセルされる

回避方法:
親スコープのキャンセルが子コルーチンに影響しないようにする場合は、supervisorScopeを使用する。

val job = GlobalScope.launch {
    supervisorScope {
        launch {
            delay(2000L)
            println("Task completed")
        }
    }
}
delay(1000L)
job.cancel() // 他の子コルーチンは継続される

5. 非同期処理のタイミングミス

落とし穴:
launchasyncで非同期処理を開始した後、適切に結果を待機しないことで処理が予期せず終了する。

:

fun main() {
    GlobalScope.launch {
        delay(1000L)
        println("Task completed")
    }
    println("Main function completed")
}

回避方法:
非同期処理が完了するまで適切に待機する。

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("Task completed")
    }
    println("Main function completed")
}

まとめ

  • エラー処理: coroutineScopesupervisorScopeのエラー伝播の違いを理解し、適切なエラーハンドリングを行う。
  • ネストの最小化: 不必要なスコープのネストを避け、シンプルな構造を保つ。
  • スコープのキャンセル: 親スコープのキャンセルが子コルーチンに与える影響を理解する。
  • 適切な待機: 非同期処理が完了するまで適切に待機することで、タイミングミスを防ぐ。

これらのポイントを押さえることで、Kotlinの非同期プログラミングをより効率的かつ安全に行えます。

応用例とベストプラクティス


KotlinのcoroutineScopesupervisorScopeを効果的に活用するためには、実際のシナリオに即した応用例やベストプラクティスを知ることが重要です。以下では、これらのスコープを使った実践的な例と、それに伴うベストプラクティスを紹介します。


1. ネットワークリクエストとデータベース処理の並行実行

シナリオ:
ネットワークからデータを取得し、データベースに保存する処理を同時に行う。coroutineScopeを使用して、すべての処理が正常に完了することを保証します。

import kotlinx.coroutines.*

fun main() = runBlocking {
    coroutineScope {
        val networkJob = async {
            fetchDataFromNetwork()
        }

        val dbJob = async {
            saveDataToDatabase()
        }

        println("Network result: ${networkJob.await()}")
        println("Database save result: ${dbJob.await()}")
    }
}

suspend fun fetchDataFromNetwork(): String {
    delay(1000L)
    return "Data fetched from network"
}

suspend fun saveDataToDatabase(): String {
    delay(500L)
    return "Data saved to database"
}

ベストプラクティス:

  • asyncを使用して非同期タスクを並行実行し、結果をまとめて取得する。
  • エラー処理を追加して、どちらかのタスクが失敗した場合に適切に処理する。

2. 複数の独立したAPIリクエストの処理

シナリオ:
複数のAPIリクエストを並行して処理し、1つのリクエストが失敗しても他のリクエストを続行する。supervisorScopeを使用して、エラーが個別に処理されるようにします。

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        val userJob = launch {
            try {
                println(fetchUserData())
            } catch (e: Exception) {
                println("Failed to fetch user data: ${e.message}")
            }
        }

        val productJob = launch {
            try {
                println(fetchProductData())
            } catch (e: Exception) {
                println("Failed to fetch product data: ${e.message}")
            }
        }

        joinAll(userJob, productJob)
    }
}

suspend fun fetchUserData(): String {
    delay(1000L)
    throw Exception("User API error")
}

suspend fun fetchProductData(): String {
    delay(1500L)
    return "Product data fetched successfully"
}

ベストプラクティス:

  • supervisorScopeを使用して独立したタスクを管理し、エラーが発生しても他のタスクに影響を与えないようにする。
  • 個別のtry-catchでエラーを適切に処理し、問題が起きたタスクだけエラーハンドリングを行う。

3. UI更新とバックグラウンド処理

シナリオ:
Androidアプリで、バックグラウンドでデータを取得し、完了後にUIを更新する。

import kotlinx.coroutines.*

fun fetchAndDisplayData() {
    CoroutineScope(Dispatchers.Main).launch {
        try {
            val data = withContext(Dispatchers.IO) {
                fetchData()
            }
            updateUI(data)
        } catch (e: Exception) {
            showError(e.message ?: "Unknown error")
        }
    }
}

suspend fun fetchData(): String {
    delay(1000L)
    return "Fetched data successfully"
}

fun updateUI(data: String) {
    println("Updating UI with: $data")
}

fun showError(message: String) {
    println("Error: $message")
}

ベストプラクティス:

  • Dispatchers.IOでバックグラウンド処理を行い、Dispatchers.MainでUIを更新する。
  • エラー処理を適切に行い、UIにエラー状態を反映する。

4. タイムアウト処理

シナリオ:
特定の処理が一定時間内に完了しない場合、タイムアウトでキャンセルする。

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        withTimeout(1500L) {
            delay(2000L) // Simulate a long-running task
            println("Task completed")
        }
    } catch (e: TimeoutCancellationException) {
        println("Task timed out")
    }
}

ベストプラクティス:

  • withTimeoutを使用して、処理時間の上限を設定することでシステムの応答性を向上させる。

まとめ

  • 適切なスコープ選択: 依存関係のあるタスクにはcoroutineScope、独立したタスクにはsupervisorScopeを使用。
  • エラーハンドリング: 個別のtry-catchを用いて柔軟にエラー処理を行う。
  • Dispatcherの活用: バックグラウンド処理にはDispatchers.IO、UI更新にはDispatchers.Mainを使用。
  • タイムアウト処理: 長時間の処理にはタイムアウトを設定し、システムの安定性を確保する。

これらの応用例とベストプラクティスを活用することで、Kotlinのコルーチンを効率的かつ効果的に管理できます。

まとめ


本記事では、KotlinにおけるcoroutineScopesupervisorScopeの違い、特徴、および実用的な使い方について詳しく解説しました。

  • coroutineScopeは、依存関係があるタスクを安全に管理し、エラーが発生した際には全体の処理を中断するスコープです。
  • supervisorScopeは、独立したタスクを並行実行し、エラーが発生しても他のタスクに影響を与えずに続行できるスコープです。

それぞれの特性に応じた選択ガイドラインや、具体的なコード例、よくある落とし穴とその回避方法、さらには実際のアプリケーションでのベストプラクティスも紹介しました。

これらの知識を活用することで、Kotlinでの非同期処理を効率的に管理し、エラー耐性の高い堅牢なアプリケーションを構築できます。

コメント

コメントする

目次
  1. coroutineScopeとは何か
    1. coroutineScopeの特徴
    2. 基本的な構文
    3. エラー伝播の例
  2. supervisorScopeとは何か
    1. supervisorScopeの特徴
    2. 基本的な構文
    3. エラーが分離される理由
    4. エラーハンドリングの例
  3. coroutineScopeとsupervisorScopeの違い
    1. エラーハンドリングの違い
    2. キャンセルの伝播
    3. ユースケースの違い
    4. まとめ表
    5. どちらを選ぶべきか
  4. coroutineScopeの実用例
    1. 例1:複数の非同期タスクの並行処理
    2. 例2:エラーが発生した場合の挙動
    3. 例3:依存関係のあるタスクの管理
    4. まとめ
  5. supervisorScopeの実用例
    1. 例1:複数の独立したタスクの並行処理
    2. 例2:エラーハンドリングを個別に行う
    3. 例3:非同期APIリクエストの実行
    4. まとめ
  6. どちらを選択すべきか
    1. 1. 依存関係のあるタスクの場合
    2. 2. 独立したタスクの場合
    3. 3. エラーハンドリングが重要な場合
    4. 4. 親子関係のタスク管理
    5. 選択ガイドライン
    6. 具体的なシナリオ別の選択
    7. まとめ
  7. よくある落とし穴とその回避方法
    1. 1. エラー処理の誤解
    2. 2. `supervisorScope`でエラーを放置する
    3. 3. スコープの誤ったネスト
    4. 4. 親スコープのキャンセルによる子コルーチンのキャンセル
    5. 5. 非同期処理のタイミングミス
    6. まとめ
  8. 応用例とベストプラクティス
    1. 1. ネットワークリクエストとデータベース処理の並行実行
    2. 2. 複数の独立したAPIリクエストの処理
    3. 3. UI更新とバックグラウンド処理
    4. 4. タイムアウト処理
    5. まとめ
  9. まとめ