web-dev-qa-db-ja.com

lifecycleScopeがスーパーバイザーである場合、その子コルーチンの失敗によりアプリがクラッシュするのはなぜですか?

私はコトリンコルーチンに不慣れで、監督を理解しようとしています。ドキュメントが言うように:

子の失敗または取り消しによって、スーパーバイザージョブが失敗することはなく、他の子に影響を与えることもありません。


OK、JVM用に次のコードを書きました。

_@JvmStatic
fun main(args: Array<String>) = runBlocking {
    val supervisorScope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    // Coroutine #1
    supervisorScope.launch {
        println("Coroutine #1 start")
        delay(100)
        throw RuntimeException("Coroutine #1 failure")
    }

    // Coroutine #2
    supervisorScope.launch {
        for (i in 0 until 5) {
            println("Coroutine #2: $i")
            delay(100)
        }
    }

    supervisorScope.coroutineContext[Job]!!.children.forEach { it.join() }
}
_

ここではすべてが正常です。_Coroutine #1_の失敗は親にも影響しませんし、_Coroutine #2_にも影響しません。それが監督の目的です。出力はドキュメントと一致しています:

_Coroutine #1 start
Coroutine #2: 0
Coroutine #2: 1
Exception in thread "DefaultDispatcher-worker-1" Java.lang.RuntimeException: Coroutine #1 failure
    at supervisor.SupervisorJobUsage$main$1$1.invokeSuspend(SupervisorJobUsage.kt:16)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Coroutine #2: 2
Coroutine #2: 3
Coroutine #2: 4

Process finished with exit code 0
_

しかし、私はAndroidとほぼ同じコードを記述しました。

_class CoroutineJobActivity : AppCompatActivity() {

    private val TAG = "CoroutineJobActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        testSupervisorScope()
    }

    private fun testSupervisorScope() {
        // Coroutine #1
        lifecycleScope.launch(Dispatchers.Default) {
            Log.d(TAG, "testSupervisorScope: Coroutine #1 start")
            delay(100)
            throw RuntimeException("Coroutine #1 failure")
        }

        // Coroutine #2
        lifecycleScope.launch(Dispatchers.Default) {
            for (i in 0 until 5) {
                Log.d(TAG, "testSupervisorScope: Coroutine #2: $i")
                delay(100)
            }
        }
    }
}
_

_Coroutine #2_がアプリのクラッシュのために作業を完了しないため、出力は予期されません。

_testSupervisorScope: Coroutine #1 start
testSupervisorScope: Coroutine #2: 0
testSupervisorScope: Coroutine #2: 1
testSupervisorScope: Coroutine #2: 2
FATAL EXCEPTION: DefaultDispatcher-worker-2
    Process: jp.neechan.kotlin_coroutines_Android, PID: 23561
    Java.lang.RuntimeException: Coroutine #1 failure
        at jp.neechan.kotlin_coroutines_Android.coroutinejob.CoroutineJobActivity$testSupervisorScope$1.invokeSuspend(CoroutineJobActivity.kt:25)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
_

_lifecycleScope.coroutineContext_はSupervisorJob() + Dispatchers.Main.immediateですが、子コルーチンの失敗が親と他の子に影響を与えていることがわかります。

では、lifecycleScopeを監督する目的は何ですか?

5
Yamashiro Rion

問題は、SupervisorJobが期待どおりに機能しないことです。 SupervisorScopeの考え方は、例外がその子の1つによって起動されても、他の子の実行をキャンセルしないことですが、例外がCancellationExceptionでない場合は、例外をシステムとそれをキャッチしない場合、アプリがクラッシュします。例外を管理するもう1つの方法は、子によって起動された例外を管理する必要があるCoroutineExceptionHandlerをスコープに渡すことです。

0
Pierluigi