kotlin协程使用withContext获取非阻塞代码时withTimeout不会取消

cod*_*tim 5 kotlin kotlin-coroutines

我正在使用 withContext 将函数转换为不会阻塞调用线程的挂起函数。为此,我使用https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761作为参考。

现在我想调用这个函数并设置超时。为此,我使用 withTimeout 来调用该函数,如下所示:

@Test
internal fun timeout() {
    runBlocking {
        logger.info("launching")
        try {
            withTimeout(1000) {
                execute()
            }
        } catch (e: TimeoutCancellationException) {
            logger.info("timed out", e)
        }
    }
}

private suspend fun execute() {
    withContext(Dispatchers.IO) {
        logger.info("sleeping")
        Thread.sleep(2000)
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我期望的是,在 1000 毫秒后,异步启动的协程将被取消,并引发 TimeoutCancellationException。
但发生的情况是,完整的 2000 毫秒经过,当协程完成时,抛出异常:

14:46:29.231 [main @coroutine#1] INFO btccCoroutineControllerTest - 启动
14:46:29.250 [DefaultDispatcher-worker-1 @coroutine#1] INFO btccCoroutineControllerTest - 睡眠
14:46:31.261 [main@coroutine#1] INFO btccCoroutineControllerTest - kotlinx.coroutines.TimeoutCancellationException 超时:在 kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:128) 在 kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:94) 在 kotlinx.coroutines 等待 1000 毫秒超时。 EventLoopImplBase$DelayedRunnableTask.run(EventLoop.kt:307) 在 kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116) 在 kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68) 在 java.lang.Thread.run (线程.java:748)

我使用了错误的东西吗?

或者这可能是预期的行为?在文档中,计数器也达到 2,这意味着协程被取消之前已经过去了 1500 毫秒: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/cancellation-and-timeouts.md#timeout

cod*_*tim 4

重新阅读有关取消的文档后,似乎协程必须合作才能取消:

\n\n
\n

协程取消是合作性的。协程代码必须\n 配合才能被取消。

\n
\n\n

https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#cancellation-is-cooperative

\n\n

我还发现设计的线程不会被中断:

\n\n
\n

取消协程不会中断线程。这是经过设计的,因为不幸的是,许多 Java 库在中断的线程中错误地运行。

\n
\n\n

https://discuss.kotlinlang.org/t/calling-blocking-code-in-coroutines/2368/6

\n\n

这解释了为什么代码等待睡眠完成。
\n这也意味着无法在阻止线程添加超时的协程上使用 withTimeout。
\n当使用返回 futures 的非阻塞库时,可以按照此处概述的方式使用 withTimeout :

\n\n
\n

为了正确地与取消集成,\n CompletableFuture.await() 使用与所有 future\n 组合器相同的约定\xe2\x80\x8a\xe2\x80\x94\xe2\x80\x8ait 取消底层未来,如果wait 调用\n 本身被取消。

\n
\n\n

https://medium.com/@elizarov/futures-cancellation-and-coroutines-b5ce9c3ede3a

\n\n

文档中示例的旁注:\n通过将日志语句添加到延迟/超时示例,我发现只有 1300 毫秒通过,因此延迟与 withTimeout 完美配合。

\n\n
\n

08:02:24.736 [main @coroutine#1] INFO btccCoroutineControllerTest\n - 我正在睡觉 0 ...
\n 08:02:25.242 [main @coroutine#1] INFO btccCoroutineControllerTest - 我正在睡觉 1 。 ..
\n 08:02:25.742 [main @coroutine#1] INFO btccCoroutineControllerTest - 我正在睡觉 2 ...
\n 08:02:26.041 [main @coroutine#1] INFO btccCoroutineControllerTest - 已取消

\n
\n