为什么在kotlin协同程序中启动吞下异常?

Zak*_*rdi 4 kotlin kotlin-coroutines

以下测试成功Process finished with exit code 0.注意,此测试会将异常打印到日志中,但不会使测试失败(这是我想要的行为).

@Test
fun why_does_this_test_pass() {
    val job = launch(Unconfined) {
        throw IllegalStateException("why does this exception not fail the test?")
    }

    // because of `Unconfined` dispatcher, exception is thrown before test function completes
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这个测试失败了 Process finished with exit code 255

@Test
fun as_expected_this_test_fails() {
    throw IllegalStateException("this exception fails the test")
}
Run Code Online (Sandbox Code Playgroud)

为什么这些测试的行为方式不一样?

Rom*_*rov 6

将您的测试与不使用任何协同程序的测试进行比较,但是开始一个新线程:

@Test
fun why_does_this_test_pass() {
    val job = thread { // <-- NOTE: Changed here
        throw IllegalStateException("why does this exception not fail the test?")
    }
    // NOTE: No need for runBlocking any more
    job.join() // ensures exception is thrown before test function completes
}
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?就像测试一样launch,如果你运行它,这个测试就会通过,但异常会在控制台上打印出来.

因此,使用launch启动新协程非常类似于使用thread启动新线程.如果失败,该错误被在未捕获的异常处理程序处理thread,并通过CoroutineExceptionHandler(见它的文档)的launch.在推出异常不吞咽,但处理由协程异常处理程序.

如果你想例外传播到测试,您应更换launchasync和替换joinawait在你的代码.另请参阅此问题:Kotlin协程中的launch/join和async/await之间有什么区别

更新:Kotlin协程最近引入了"结构化并发"的概念,以避免这种异常丢失.此问题中的代码不再编译.要编译它,你必须明确地说GlobalScope.launch(如"我确认它可以解除我的异常,这是我的签名")或将测试包装进去runBlocking { ... },在这种情况下异常不会丢失.