私はコルーチンで遊んでいて、いくつかの非常に奇妙な振る舞いを見つけました。 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")
}
}
_
2番目の例の動作は正しいです。これは、構造化された同時実行の動作です。内側のasync
ブロックは例外をスローするため、このコルーチンはキャンセルされます。構造化された同時実行性により、親ジョブもキャンセルされます。
この小さな例を見てください:
val result = coroutineScope {
async {
throw IllegalStateException()
}
10
}
async
の結果をリクエストしなくても、このブロックは値を返しません。内側のコルーチンがキャンセルされ、外側のスコープもキャンセルされます。
この動作が気に入らない場合は、supervisorScope
を使用できます。この場合、外側のコルーチンが失敗することなく、内側のコルーチンが失敗する可能性があります。
val result = supervisorScope {
async {
throw IllegalStateException()
}
10
}
最初の例では、コルーチンブロック内で例外をキャッチします。このため、コルーチンは正常に終了します。
このトピックの説明については、以下を参照してください。
昨日、この振る舞いに衝撃を受けました ここに私の分析があります 。
簡単に言えば、async
には他の言語と同じ目的がないため、この動作が望ましいです。 Kotlinでは、タスクを並行して実行する複数のサブタスクに分解する必要がある場合にのみ、控えめに使用する必要があります。
書きたいときはいつでも
_val result = async { work() }.await()
_
代わりに書く必要があります
_val result = withContext(Default) { work() }
_
これは期待どおりに動作します。また、機会があればいつでも、withContext
呼び出しをwork()
関数に移動し、_suspend fun
_にする必要があります。