Kotlin协程无法处理异常

时间:2018-11-10 11:29:48

标签: exception kotlin kotlin-coroutines

我在和协程一起玩耍,发现一些非常奇怪的行为。我想使用suspendCoroutine()在项目中转换一些异步请求。这是显示此问题的代码。

在第一种情况下,当在runBlocking协程中调用suspend函数时,来自延续的异常进入catch块,然后runBlocking成功完成。但是在第二种情况下,当创建新的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

关于发生这种情况的任何想法-是bug,还是我以错误的方式使用协同程序?

更新

使用coroutineScope { ... }将减轻runFailingCoroutine()中的问题

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

2 个答案:

答案 0 :(得分:2)

第二个示例的行为是正确的,这是结构化并发的工作。 由于内部async块引发异常,因此该协程被取消。由于结构性并发,父作业也被取消。

看这个小例子:

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

即使我们从不请求async结果,该块也将永远不会返回值。内部协程被取消,外部范围也被取消。

如果您不喜欢这种行为,则可以使用supervisorScope。在这种情况下,内部协程可能会失败,而外部协程不会失败。

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

在第一个示例中,您在协程块中捕获了异常,因此,协程正常退出。

有关此主题的讨论,请参见:

答案 1 :(得分:1)

昨天here's my analysis,我被这种行为所震惊。

简而言之,这种行为是需要的,因为async的目的与其他语言不同。在Kotlin中,只有在必须将一个任务分解为几个并行运行的子任务时,才应谨慎使用它。

只要您想写

val result = async { work() }.await()

您应该改写

val result = withContext(Default) { work() }

,这将符合预期的方式。另外,只要有机会,就应该将withContext调用移到work()函数中,并将其设为suspend fun