web-dev-qa-db-ja.com

Kotlinコルーチンは例外を処理できません

私はコルーチンで遊んでいて、いくつかの非常に奇妙な振る舞いを見つけました。 suspendCoroutine()を使用して、プロジェクトの非同期リクエストを変換したい。この問題を示すコードは次のとおりです。

最初の場合、サスペンド関数がrunBlockingコルーチンで呼び出されると、継続からの例外がcatchブロックに移動し、runBlockingが正常に終了します。しかし、2番目の場合、新しいasyncコルーチンを作成するとき、例外はcatchブロックを通過し、プログラム全体をクラッシュさせます。

_package com.example.lib

import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

object Test {
    fun runSuccessfulCoroutine() {
        runBlocking {
            try {
                Repository.fail()
            } catch (ex: Throwable) {
                println("Catching ex in runSuccessfulCoroutine(): $ex")
            }
        }
    }

    fun runFailingCoroutine() {
        runBlocking {
            try {
                async { Repository.fail() }.await()
            } catch (ex: Throwable) {
                println("Catching ex in runFailingCoroutine(): $ex")
            }
        }
    }
}

object Repository {
    suspend fun fail(): Int = suspendCoroutine { cont ->
        cont.resumeWithException(RuntimeException("Exception at ${Thread.currentThread().name}"))
    }
}


fun main() {
    Test.runSuccessfulCoroutine()
    println()

    Test.runFailingCoroutine()

    println("We will never get here")
}
_

それはコンソールに印刷されるものです:

_Catching ex in runSuccessfulCoroutine(): Java.lang.RuntimeException: Exception at main

Catching ex in runFailingCoroutine(): Java.lang.RuntimeException: Exception at main
Exception in thread "main" Java.lang.RuntimeException: Exception at main
    at com.example.lib.Repository.fail(MyClass.kt:32)
    at com.example.lib.Test$runFailingCoroutine$1$1.invokeSuspend(MyClass.kt:22)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
    at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:69)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at com.example.lib.Test.runFailingCoroutine(MyClass.kt:20)
    at com.example.lib.MyClassKt.main(MyClass.kt:41)
    at com.example.lib.MyClassKt.main(MyClass.kt)

Process finished with exit code 1
_

これが起こっている理由はありますか?それはバグですか、それともコルーチンを間違った方法で使用していますか?

更新:

_coroutineScope { ... }_を使用すると、runFailingCoroutine()の問題を軽減できます

_fun runFailingCoroutine() = runBlocking {
    try {
        coroutineScope { async { fail() }.await()  }
    } catch (ex: Throwable) {
        println("Catching ex in runFailingCoroutine(): $ex")
    }
}
_
7

2番目の例の動作は正しいです。これは、構造化された同時実行の動作です。内側のasyncブロックは例外をスローするため、このコルーチンはキャンセルされます。構造化された同時実行性により、親ジョブもキャンセルされます。

この小さな例を見てください:

val result = coroutineScope {
    async {
        throw IllegalStateException()
    }
    10
}

asyncの結果をリクエストしなくても、このブロックは値を返しません。内側のコルーチンがキャンセルされ、外側のスコープもキャンセルされます。

この動作が気に入らない場合は、supervisorScopeを使用できます。この場合、外側のコルーチンが失敗することなく、内側のコルーチンが失敗する可能性があります。

val result = supervisorScope {
    async {
        throw IllegalStateException()
    }
    10
}

最初の例では、コルーチンブロック内で例外をキャッチします。このため、コルーチンは正常に終了します。

このトピックの説明については、以下を参照してください。

2
Rene

昨日、この振る舞いに衝撃を受けました ここに私の分析があります

簡単に言えば、asyncには他の言語と同じ目的がないため、この動作が望ましいです。 Kotlinでは、タスクを並行して実行する複数のサブタスクに分解する必要がある場合にのみ、控えめに使用する必要があります。

書きたいときはいつでも

_val result = async { work() }.await()
_

代わりに書く必要があります

_val result = withContext(Default) { work() }
_

これは期待どおりに動作します。また、機会があればいつでも、withContext呼び出しをwork()関数に移動し、_suspend fun_にする必要があります。

3
Marko Topolnik