Kotlinでコルーチンの例外処理をマスター!SupervisorJobの活用法を徹底解説

Kotlinの非同期処理において、コルーチンは非常に強力なツールです。しかし、非同期タスクの中でエラーが発生した場合、適切に例外を処理しないとアプリケーションがクラッシュしたり、他のタスクに悪影響を及ぼしたりすることがあります。特に、複数のコルーチンを並行して実行していると、一つのコルーチンでのエラーが親ジョブや他の子コルーチンに伝播し、全体の処理が停止してしまうことがあります。

こういった問題を防ぐために、KotlinではSupervisorJobSupervisorScopeといった仕組みが提供されています。これにより、一部のコルーチンでエラーが発生しても、他のコルーチンへの影響を抑え、安定した非同期処理が実現できます。本記事では、SupervisorJobを使った例外処理の方法や具体的な活用例を詳しく解説していきます。

目次
  1. Kotlinコルーチンにおける例外処理の基本
    1. 通常の例外処理とコルーチンの違い
    2. 例外処理の基本構文
    3. エラーが親ジョブに伝播する例
  2. SupervisorJobとは何か
    1. SupervisorJobの仕組み
    2. 通常のJobとSupervisorJobの比較
    3. SupervisorJobの用途
  3. SupervisorJobを使った例外処理の実装
    1. SupervisorJobの基本的な使い方
    2. 解説
    3. 複数のタスクを並行して実行する例
    4. SupervisorJobのポイント
  4. 親子コルーチンと例外の伝播
    1. 通常のJobにおける例外の伝播
    2. SupervisorJobにおける例外の伝播
    3. 親子コルーチンの関係性
    4. 例外の伝播を管理するポイント
  5. SupervisorScopeの活用法
    1. SupervisorScopeの基本的な使い方
    2. SupervisorScopeの基本構文
    3. SupervisorScopeと通常のCoroutineScopeの違い
    4. SupervisorScopeの実用例
    5. SupervisorScopeを使用する際のポイント
  6. 例外処理におけるtry-catchの活用
    1. launchでのtry-catchの使い方
    2. asyncでのtry-catchの使い方
    3. SupervisorJobとtry-catchの組み合わせ
    4. try-catchを使う際のポイント
  7. 実際の開発での応用例
    1. 応用例1: 複数のAPIリクエストの並行実行
    2. 応用例2: ユーザーインターフェース操作とバックグラウンド処理
    3. 応用例3: バッチ処理のエラー管理
    4. 応用のポイント
  8. よくあるエラーとそのトラブルシューティング
    1. 1. 子コルーチンでの例外が親に伝播する
    2. 2. asyncの例外が未処理のままになる
    3. 3. CancellationExceptionを見落とす
    4. 4. コルーチンのスコープが適切でない
    5. 5. 未処理の例外がアプリケーションをクラッシュさせる
    6. トラブルシューティングのポイント
  9. まとめ

Kotlinコルーチンにおける例外処理の基本


Kotlinのコルーチンでは、非同期処理中に例外が発生した場合、適切にキャッチして処理しなければ予期せぬ動作やクラッシュを引き起こします。コルーチンの例外処理には、基本的なルールと仕組みが存在します。

通常の例外処理とコルーチンの違い


通常の関数内ではtry-catchブロックで例外をキャッチしますが、コルーチンの場合は、次のポイントを理解しておく必要があります:

  • Launchビルダーlaunchで作成したコルーチンが例外をスローすると、その例外は親ジョブに伝播し、親ジョブがキャンセルされます。
  • Asyncビルダーasyncで作成したコルーチンが例外をスローした場合、await()を呼び出したタイミングで例外が再スローされます。

例外処理の基本構文


コルーチン内での例外をキャッチするには、以下のようにtry-catchを使用します。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            throw Exception("エラー発生")
        } catch (e: Exception) {
            println("例外キャッチ: ${e.message}")
        }
    }
    job.join()
}

エラーが親ジョブに伝播する例


以下のコードは、親コルーチンが子コルーチンの例外によってキャンセルされる例です。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        val childJob = launch {
            throw Exception("子コルーチンでエラー発生")
        }
        try {
            childJob.join()
        } catch (e: Exception) {
            println("親ジョブがエラーをキャッチ: ${e.message}")
        }
    }
    parentJob.join()
}

このように、コルーチンでは例外処理の仕組みを正しく理解することで、エラーによる影響をコントロールできます。次のセクションでは、こうした影響を抑えるためのSupervisorJobについて解説します。

SupervisorJobとは何か


KotlinのSupervisorJobは、親子関係にあるコルーチンのエラー伝播を制御するために使用されます。通常のJobでは、子コルーチンがエラーをスローすると、そのエラーが親コルーチンに伝播し、親および他の子コルーチンもキャンセルされてしまいます。しかし、SupervisorJobを使うと、子コルーチンのエラーが他の子コルーチンや親コルーチンに影響を与えません。

SupervisorJobの仕組み


SupervisorJobの主な特徴は次の通りです:

  1. 独立したエラー処理:子コルーチンがエラーをスローしても、他の子コルーチンには影響しません。
  2. 親コルーチンは影響を受けない:エラーが発生しても、親ジョブがキャンセルされることはありません。

通常のJobとSupervisorJobの比較

通常のJobの場合:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        val child1 = launch {
            println("子1が開始")
            delay(1000)
            throw Exception("子1でエラー発生")
        }
        val child2 = launch {
            println("子2が開始")
            delay(2000)
            println("子2が完了")
        }
    }
    parentJob.join()
}

出力例:

子1が開始  
子2が開始  
Exception in thread "main" java.lang.Exception: 子1でエラー発生

エラーにより親ジョブと子2もキャンセルされ、子2は完了できません。

SupervisorJobを使った場合:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val parentJob = launch(supervisor) {
        val child1 = launch {
            println("子1が開始")
            delay(1000)
            throw Exception("子1でエラー発生")
        }
        val child2 = launch {
            println("子2が開始")
            delay(2000)
            println("子2が完了")
        }
    }
    parentJob.join()
}

出力例:

子1が開始  
子2が開始  
子2が完了  
Exception in thread "main" java.lang.Exception: 子1でエラー発生

SupervisorJobを使用することで、子1がエラーをスローしても、子2は影響を受けずに処理を完了します。

SupervisorJobの用途


SupervisorJobは、次のような場面で有効です:

  • 複数の独立したタスクを同時に実行する場合
    あるタスクが失敗しても、他のタスクには影響を与えたくないときに便利です。
  • 安定したUI操作
    UIスレッドでコルーチンを使用する際、個々のタスクが独立して処理されることが求められる場合に役立ちます。

次のセクションでは、SupervisorJobを使った具体的な実装例について詳しく解説します。

SupervisorJobを使った例外処理の実装


SupervisorJobを使用すると、子コルーチンのエラーが他のコルーチンに影響を与えないように制御できます。ここでは、SupervisorJobを使った具体的な例外処理の実装方法を紹介します。

SupervisorJobの基本的な使い方


SupervisorJobを親ジョブとして設定し、複数の子コルーチンを独立して管理する方法です。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()

    val scope = CoroutineScope(supervisor + Dispatchers.Default)

    val job1 = scope.launch {
        try {
            println("Job1が開始")
            delay(1000)
            throw Exception("Job1でエラー発生")
        } catch (e: Exception) {
            println("Job1の例外キャッチ: ${e.message}")
        }
    }

    val job2 = scope.launch {
        println("Job2が開始")
        delay(2000)
        println("Job2が正常に完了")
    }

    // すべてのジョブが終了するのを待つ
    joinAll(job1, job2)
    println("すべてのタスクが終了しました")
}

出力結果:

Job1が開始  
Job2が開始  
Job1の例外キャッチ: Job1でエラー発生  
Job2が正常に完了  
すべてのタスクが終了しました

解説

  • SupervisorJobの作成SupervisorJob()を作成し、CoroutineScopeに渡します。これにより、子コルーチンがエラーをスローしても他の子コルーチンに影響しません。
  • 個別のエラー処理try-catchブロックを使用して、各子コルーチンで発生した例外を個別にキャッチします。
  • ジョブの待機joinAllを使用して、すべての子コルーチンの完了を待ちます。

複数のタスクを並行して実行する例


以下は複数の子コルーチンを並行して実行し、いずれかのタスクでエラーが発生しても他のタスクが正常に続行する例です。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor + Dispatchers.Default)

    val jobs = listOf(
        scope.launch {
            delay(500)
            println("タスク1が正常に完了")
        },
        scope.launch {
            delay(1000)
            throw Exception("タスク2でエラー発生")
        },
        scope.launch {
            delay(1500)
            println("タスク3が正常に完了")
        }
    )

    jobs.forEach {
        it.invokeOnCompletion { throwable ->
            if (throwable != null) {
                println("エラー: ${throwable.message}")
            }
        }
    }

    jobs.joinAll()
    println("すべてのタスクが終了しました")
}

出力結果:

タスク1が正常に完了  
エラー: タスク2でエラー発生  
タスク3が正常に完了  
すべてのタスクが終了しました

SupervisorJobのポイント

  • エラーの局所化SupervisorJobを使うことで、エラーが局所化され、影響範囲を抑えられます。
  • 安定性の向上:一つのタスクの失敗が全体の処理を止めるリスクを軽減します。

次のセクションでは、親子コルーチン間のエラー伝播についてさらに詳しく解説します。

親子コルーチンと例外の伝播


Kotlinのコルーチンは階層構造(親子関係)で動作するため、子コルーチンで発生した例外は親コルーチンに伝播します。通常のJobSupervisorJobでは、この例外の伝播の挙動が異なります。

通常のJobにおける例外の伝播


親コルーチンが通常のJobを持っている場合、子コルーチンで例外が発生すると、その例外が親に伝播し、親および他の子コルーチンもキャンセルされます。

例:通常のJobの場合

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        val child1 = launch {
            println("子1が開始")
            delay(1000)
            throw Exception("子1でエラー発生")
        }
        val child2 = launch {
            println("子2が開始")
            delay(2000)
            println("子2が完了")
        }
    }

    try {
        parentJob.join()
    } catch (e: Exception) {
        println("親ジョブで例外キャッチ: ${e.message}")
    }
}

出力結果:

子1が開始  
子2が開始  
親ジョブで例外キャッチ: 子1でエラー発生

この場合、子1でエラーが発生すると、親ジョブおよび子2もキャンセルされます。

SupervisorJobにおける例外の伝播


SupervisorJobを使用すると、子コルーチンで発生した例外が親に伝播しなくなります。他の子コルーチンは影響を受けず、独立して処理を続行できます。

例:SupervisorJobの場合

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()

    val parentJob = launch(supervisor) {
        val child1 = launch {
            println("子1が開始")
            delay(1000)
            throw Exception("子1でエラー発生")
        }
        val child2 = launch {
            println("子2が開始")
            delay(2000)
            println("子2が完了")
        }
    }

    parentJob.join()
    println("親ジョブが完了")
}

出力結果:

子1が開始  
子2が開始  
子2が完了  
親ジョブが完了

親子コルーチンの関係性

  • 通常のJob:子コルーチンで例外が発生すると、親と他の子コルーチンもキャンセルされます。
  • SupervisorJob:子コルーチンの例外は親コルーチンに伝播しません。他の子コルーチンも独立して処理されます。

例外の伝播を管理するポイント

  • タスクの独立性が重要な場合はSupervisorJobを使用する。
  • 全体の処理が一貫している必要がある場合は、通常のJobを使用する。

次のセクションでは、SupervisorScopeを活用した具体的な例外処理について解説します。

SupervisorScopeの活用法


SupervisorScopeは、KotlinコルーチンでSupervisorJobを簡単に利用できるスコープです。SupervisorScope内で起動した子コルーチンは、エラーが発生しても他の子コルーチンに影響を与えません。これにより、独立したタスクを並行して実行しながら、効率よくエラー処理ができます。

SupervisorScopeの基本的な使い方


SupervisorScopeは、CoroutineScopeと同様に非同期タスクの管理を行いますが、以下の特徴があります:

  • 子コルーチンのエラーが親に伝播しない:エラーが発生しても、他の子コルーチンはキャンセルされません。
  • 独立したタスク管理:複数のタスクが独立して処理され、失敗したタスクが他に影響を与えません。

SupervisorScopeの基本構文

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        val job1 = launch {
            try {
                println("Job1が開始")
                delay(1000)
                throw Exception("Job1でエラー発生")
            } catch (e: Exception) {
                println("Job1の例外キャッチ: ${e.message}")
            }
        }

        val job2 = launch {
            println("Job2が開始")
            delay(2000)
            println("Job2が正常に完了")
        }

        println("SupervisorScope内の処理が完了")
    }

    println("runBlockingの処理が完了")
}

出力結果:

Job1が開始  
Job2が開始  
Job1の例外キャッチ: Job1でエラー発生  
Job2が正常に完了  
SupervisorScope内の処理が完了  
runBlockingの処理が完了

SupervisorScopeと通常のCoroutineScopeの違い

特徴CoroutineScopeSupervisorScope
エラーの伝播子コルーチンのエラーが親に伝播する子コルーチンのエラーが親に伝播しない
子コルーチンの独立性他の子コルーチンもキャンセルされる他の子コルーチンは独立して処理される
適したシナリオタスクが相互依存している場合タスクが独立している場合

SupervisorScopeの実用例

以下の例では、複数のネットワークリクエストを並行して実行し、1つのリクエストが失敗しても他のリクエストに影響を与えないようにしています。

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        val request1 = launch {
            println("リクエスト1を開始")
            delay(1000)
            throw Exception("リクエスト1でエラー発生")
        }

        val request2 = launch {
            println("リクエスト2を開始")
            delay(2000)
            println("リクエスト2が正常に完了")
        }

        val request3 = launch {
            println("リクエスト3を開始")
            delay(1500)
            println("リクエスト3が正常に完了")
        }
    }

    println("すべてのリクエスト処理が完了しました")
}

出力結果:

リクエスト1を開始  
リクエスト2を開始  
リクエスト3を開始  
リクエスト1でエラー発生  
リクエスト3が正常に完了  
リクエスト2が正常に完了  
すべてのリクエスト処理が完了しました

SupervisorScopeを使用する際のポイント

  1. 独立性が重要なタスク:タスクが互いに影響し合わない場合に適しています。
  2. 安定した処理:一つのタスクが失敗しても、他のタスクを止めたくない場合に有効です。
  3. エラー処理:各子コルーチンに個別のtry-catchを追加して、エラー処理を適切に行いましょう。

次のセクションでは、try-catchを活用した例外処理についてさらに詳しく解説します。

例外処理におけるtry-catchの活用


Kotlinのコルーチン内で例外処理を行う際、try-catchブロックはシンプルかつ効果的な方法です。特に、launchasyncで作成したコルーチン内のエラーを適切に処理するためには、try-catchを活用する必要があります。

launchでのtry-catchの使い方


launchビルダーでは、コルーチンが例外をスローすると、その時点でエラーが発生し、親コルーチンにも影響します。エラーをキャッチするには、try-catchをコルーチン内に配置します。

例:launchでのtry-catch

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            println("処理を開始")
            delay(1000)
            throw Exception("エラー発生")
        } catch (e: Exception) {
            println("例外キャッチ: ${e.message}")
        }
    }
    job.join()
    println("処理が完了しました")
}

出力結果:

処理を開始  
例外キャッチ: エラー発生  
処理が完了しました

asyncでのtry-catchの使い方


asyncビルダーでは、コルーチンが例外をスローしても即座にエラーが発生せず、await()を呼び出したときに例外がスローされます。

例:asyncでのtry-catch

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = async {
        println("非同期処理を開始")
        delay(1000)
        throw Exception("非同期処理でエラー発生")
    }

    try {
        deferred.await()
    } catch (e: Exception) {
        println("例外キャッチ: ${e.message}")
    }

    println("処理が完了しました")
}

出力結果:

非同期処理を開始  
例外キャッチ: 非同期処理でエラー発生  
処理が完了しました

SupervisorJobとtry-catchの組み合わせ


SupervisorJobを使う場合、エラーが発生しても他の子コルーチンには影響しません。各子コルーチンで個別にtry-catchを利用することで、エラー処理を独立して行えます。

例:SupervisorJobとtry-catchの活用

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor)

    val job1 = scope.launch {
        try {
            println("Job1が開始")
            delay(1000)
            throw Exception("Job1でエラー発生")
        } catch (e: Exception) {
            println("Job1の例外キャッチ: ${e.message}")
        }
    }

    val job2 = scope.launch {
        println("Job2が開始")
        delay(2000)
        println("Job2が正常に完了")
    }

    joinAll(job1, job2)
    println("すべてのタスクが完了しました")
}

出力結果:

Job1が開始  
Job2が開始  
Job1の例外キャッチ: Job1でエラー発生  
Job2が正常に完了  
すべてのタスクが完了しました

try-catchを使う際のポイント

  1. エラーを局所化する:各コルーチン内にtry-catchを配置し、エラーを局所的に処理することで全体への影響を抑えます。
  2. エラーのログ出力:エラー内容をログに出力して、問題の特定をしやすくしましょう。
  3. 適切なエラーハンドリング:単にエラーをキャッチするだけでなく、リカバリ処理や再試行のロジックを組み込むと堅牢なアプリケーションになります。

次のセクションでは、SupervisorJobを活用した実際の開発シナリオについて解説します。

実際の開発での応用例


SupervisorJobを活用することで、現実のアプリケーション開発において安定した非同期処理を実現できます。ここでは、よくある開発シナリオにおけるSupervisorJobとコルーチンの例外処理の応用例を紹介します。

応用例1: 複数のAPIリクエストの並行実行


複数のAPIリクエストを並行して実行し、1つのリクエストが失敗しても他のリクエストを継続させる例です。

import kotlinx.coroutines.*
import kotlin.random.Random

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor + Dispatchers.IO)

    val apiRequests = listOf(
        scope.launch {
            try {
                println("APIリクエスト1開始")
                delay(Random.nextLong(500, 1500))
                if (Random.nextBoolean()) throw Exception("APIリクエスト1失敗")
                println("APIリクエスト1成功")
            } catch (e: Exception) {
                println("APIリクエスト1エラー: ${e.message}")
            }
        },
        scope.launch {
            try {
                println("APIリクエスト2開始")
                delay(Random.nextLong(500, 1500))
                if (Random.nextBoolean()) throw Exception("APIリクエスト2失敗")
                println("APIリクエスト2成功")
            } catch (e: Exception) {
                println("APIリクエスト2エラー: ${e.message}")
            }
        },
        scope.launch {
            try {
                println("APIリクエスト3開始")
                delay(Random.nextLong(500, 1500))
                if (Random.nextBoolean()) throw Exception("APIリクエスト3失敗")
                println("APIリクエスト3成功")
            } catch (e: Exception) {
                println("APIリクエスト3エラー: ${e.message}")
            }
        }
    )

    apiRequests.joinAll()
    println("すべてのAPIリクエストが完了しました")
}

出力例:

APIリクエスト1開始  
APIリクエスト2開始  
APIリクエスト3開始  
APIリクエスト2エラー: APIリクエスト2失敗  
APIリクエスト1成功  
APIリクエスト3成功  
すべてのAPIリクエストが完了しました

応用例2: ユーザーインターフェース操作とバックグラウンド処理


UI操作を中断せず、バックグラウンドでデータを処理するシナリオです。

import kotlinx.coroutines.*
import kotlin.random.Random

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor + Dispatchers.Default)

    val fetchDataJob = scope.launch {
        try {
            println("データ取得開始")
            delay(1000)
            if (Random.nextBoolean()) throw Exception("データ取得エラー")
            println("データ取得成功")
        } catch (e: Exception) {
            println("エラー: ${e.message}")
        }
    }

    val updateUIJob = scope.launch {
        println("UI更新処理開始")
        delay(500)
        println("UI更新処理完了")
    }

    joinAll(fetchDataJob, updateUIJob)
    println("すべての処理が完了しました")
}

出力例:

データ取得開始  
UI更新処理開始  
UI更新処理完了  
データ取得成功  
すべての処理が完了しました

応用例3: バッチ処理のエラー管理


複数のバッチタスクを処理し、一部のタスクが失敗しても他のタスクを継続する例です。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor + Dispatchers.Default)

    val tasks = listOf(
        scope.launch {
            try {
                println("タスク1実行中")
                delay(1000)
                throw Exception("タスク1失敗")
            } catch (e: Exception) {
                println("タスク1エラー: ${e.message}")
            }
        },
        scope.launch {
            println("タスク2実行中")
            delay(2000)
            println("タスク2完了")
        },
        scope.launch {
            println("タスク3実行中")
            delay(1500)
            println("タスク3完了")
        }
    )

    tasks.joinAll()
    println("すべてのバッチ処理が完了しました")
}

出力例:

タスク1実行中  
タスク2実行中  
タスク3実行中  
タスク1エラー: タスク1失敗  
タスク3完了  
タスク2完了  
すべてのバッチ処理が完了しました

応用のポイント

  1. 独立した処理:各タスクやリクエストが独立して処理されるため、1つの失敗が全体に影響しません。
  2. エラー処理の明確化:個別にtry-catchでエラー処理を行い、問題箇所を特定しやすくします。
  3. 効率的な並行処理:バックグラウンド処理やバッチ処理が効率的に実行され、UIの応答性を維持できます。

次のセクションでは、よくあるエラーとそのトラブルシューティングについて解説します。

よくあるエラーとそのトラブルシューティング


KotlinのコルーチンとSupervisorJobを使用する際に発生しがちなエラーと、それに対する解決方法を紹介します。正しいエラーハンドリングを理解することで、より堅牢な非同期処理を実現できます。

1. 子コルーチンでの例外が親に伝播する


エラーの内容:
通常のJobを使用した場合、子コルーチンで発生した例外が親コルーチンに伝播し、親および他の子コルーチンがキャンセルされてしまいます。

解決方法:
SupervisorJobを使用することで、子コルーチンの例外が親に伝播するのを防げます。

例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor)

    val job1 = scope.launch {
        throw Exception("子コルーチンでエラー発生")
    }

    val job2 = scope.launch {
        println("子2が正常に完了")
    }

    joinAll(job1, job2)
}

2. asyncの例外が未処理のままになる


エラーの内容:
asyncを使っている場合、await()を呼び出さないと例外が未処理のままになり、予期しないクラッシュを引き起こすことがあります。

解決方法:
必ずawait()を呼び出して例外をキャッチするようにします。

例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = async {
        throw Exception("非同期処理でエラー発生")
    }

    try {
        deferred.await()
    } catch (e: Exception) {
        println("例外キャッチ: ${e.message}")
    }
}

3. CancellationExceptionを見落とす


エラーの内容:
コルーチンがキャンセルされるとCancellationExceptionがスローされますが、これを誤って一般的な例外としてキャッチすると、キャンセルが正常に処理されません。

解決方法:
CancellationExceptionはキャッチしないようにするか、特別に処理するようにします。

例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } catch (e: CancellationException) {
            println("キャンセルされました")
            throw e  // 再スローしてキャンセルを伝播
        }
    }

    delay(500)
    job.cancelAndJoin()
}

4. コルーチンのスコープが適切でない


エラーの内容:
UI関連の処理でメインスレッド以外のスレッドからUIを更新しようとするとクラッシュすることがあります。

解決方法:
UI関連のコルーチンはDispatchers.Mainで実行するようにします。

例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.Main) {
        println("UIの更新処理")
    }
}

5. 未処理の例外がアプリケーションをクラッシュさせる


エラーの内容:
例外処理を行わないと、未処理の例外がアプリケーション全体をクラッシュさせることがあります。

解決方法:
CoroutineExceptionHandlerを使用してグローバルに例外処理を行います。

例:

import kotlinx.coroutines.*

val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    println("例外キャッチ: ${exception.message}")
}

fun main() = runBlocking {
    val job = launch(exceptionHandler) {
        throw Exception("予期しないエラー発生")
    }
    job.join()
}

トラブルシューティングのポイント

  1. 例外処理を明示的に行うtry-catchCoroutineExceptionHandlerを使って例外を適切に処理しましょう。
  2. スコープを適切に選択する:UI処理にはDispatchers.Main、バックグラウンド処理にはDispatchers.IOを使用します。
  3. キャンセル処理を正しく理解するCancellationExceptionは再スローしてキャンセルの伝播を維持しましょう。

次のセクションでは、これまでの内容をまとめます。

まとめ


本記事では、Kotlinコルーチンにおける例外処理の方法と、SupervisorJobの活用法について解説しました。通常のJobでは、子コルーチンで発生した例外が親や他の子コルーチンに伝播してしまいますが、SupervisorJobを利用することで、エラーが発生しても他のタスクに影響を与えずに処理を続行できます。

また、SupervisorScopetry-catchを併用することで、より柔軟で堅牢な非同期処理が実現可能です。実際の開発シナリオにおけるAPIリクエスト、UI操作、バッチ処理の例を通して、SupervisorJobの有用性を確認しました。

エラー処理のトラブルシューティングのポイントとして、CancellationExceptionの扱いや、未処理の例外を防ぐためのCoroutineExceptionHandlerの活用も重要です。これらの知識を活かし、安定したKotlinアプリケーションを開発しましょう。

コメント

コメントする

目次
  1. Kotlinコルーチンにおける例外処理の基本
    1. 通常の例外処理とコルーチンの違い
    2. 例外処理の基本構文
    3. エラーが親ジョブに伝播する例
  2. SupervisorJobとは何か
    1. SupervisorJobの仕組み
    2. 通常のJobとSupervisorJobの比較
    3. SupervisorJobの用途
  3. SupervisorJobを使った例外処理の実装
    1. SupervisorJobの基本的な使い方
    2. 解説
    3. 複数のタスクを並行して実行する例
    4. SupervisorJobのポイント
  4. 親子コルーチンと例外の伝播
    1. 通常のJobにおける例外の伝播
    2. SupervisorJobにおける例外の伝播
    3. 親子コルーチンの関係性
    4. 例外の伝播を管理するポイント
  5. SupervisorScopeの活用法
    1. SupervisorScopeの基本的な使い方
    2. SupervisorScopeの基本構文
    3. SupervisorScopeと通常のCoroutineScopeの違い
    4. SupervisorScopeの実用例
    5. SupervisorScopeを使用する際のポイント
  6. 例外処理におけるtry-catchの活用
    1. launchでのtry-catchの使い方
    2. asyncでのtry-catchの使い方
    3. SupervisorJobとtry-catchの組み合わせ
    4. try-catchを使う際のポイント
  7. 実際の開発での応用例
    1. 応用例1: 複数のAPIリクエストの並行実行
    2. 応用例2: ユーザーインターフェース操作とバックグラウンド処理
    3. 応用例3: バッチ処理のエラー管理
    4. 応用のポイント
  8. よくあるエラーとそのトラブルシューティング
    1. 1. 子コルーチンでの例外が親に伝播する
    2. 2. asyncの例外が未処理のままになる
    3. 3. CancellationExceptionを見落とす
    4. 4. コルーチンのスコープが適切でない
    5. 5. 未処理の例外がアプリケーションをクラッシュさせる
    6. トラブルシューティングのポイント
  9. まとめ