我正在使用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)
}
}
所以我希望在1000毫秒后取消异步启动的协程,并抛出TimeoutCancellationException。
但是发生的是整个2000毫秒的传递,当协程完成时会引发异常:
14:46:29.231 [main @ coroutine#1] INFO b.t.c.c. CoroutineControllerTest -启动
14:46:29.250 [DefaultDispatcher-worker-1 @ coroutine#1]信息b.t.c.c.CoroutineControllerTest-睡眠
14:46:31.261 [main @ coroutine#1]信息b.t.c.c. CoroutineControllerTest-超时 kotlinx.coroutines.TimeoutCancellationException:等待超时 1000毫秒 kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:128) 在kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:94)在 kotlinx.coroutines.EventLoopImplBase $ DelayedRunnableTask.run(EventLoop.kt:307) 在 kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116) 在kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68)在 java.lang.Thread.run(Thread.java:748)
我使用错了吗?
也许这是预期的行为?在文档中,计数器也变为2,这意味着在取消协程之前已经过1500毫秒: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/cancellation-and-timeouts.md#timeout
答案 0 :(得分:0)
重新阅读取消文件后,协程似乎必须合作才能取消:
协程取消是合作的。协程代码必须 合作才能取消。
我还发现设计的线程不会被中断:
取消协程不会中断线程。这样做是通过 设计,因为不幸的是,许多Java库错误地 在中断的线程中运行。
https://discuss.kotlinlang.org/t/calling-blocking-code-in-coroutines/2368/6
这说明了为什么代码等待睡眠完成。
这也意味着不可能在阻止线程添加超时的协程中使用withTimeout。
当使用返回期货的非阻塞库时,可以按如下所述使用withTimeout:
为了与取消正确整合, CompletableFuture.await()使用与所有将来相同的约定 组合器会这样做—如果等待调用,它将取消潜在的未来 本身被取消了。
https://medium.com/@elizarov/futures-cancellation-and-coroutines-b5ce9c3ede3a
文档中示例的旁注: 通过在延迟/超时示例中添加日志语句,我发现只有1300毫秒通过,因此延迟与withTimeout完美配合。
08:02:24.736 [main @ coroutine#1] INFO b.t.c.c. CoroutineControllerTest -我在睡觉0 ...
08:02:25.242 [main @ coroutine#1] INFO b.t.c.c. CoroutineControllerTest-我在睡觉1 ...
08:02:25.742 [main @ coroutine#1] INFO b.t.c.c. CoroutineControllerTest-我是 睡觉2 ...
08:02:26.041 [main @ coroutine#1] INFO b.t.c.c. CoroutineControllerTest-已取消
答案 1 :(得分:0)
如果启动子协程并等待其完成,您可以在超时后取得进展:
fun timeout() {
runBlocking {
logger.info("launching")
try {
withTimeout(100) {
execute()
}
} catch (e: TimeoutCancellationException) {
logger.info("timed out", e)
}
}
}
private suspend fun execute() =
GlobalScope.launch(Dispatchers.IO) {
logger.info("sleeping")
Thread.sleep(2000)
}.join()
通过此操作,您可以将受阻止的子协程与您在其中join()
的调度程序中解耦,以便suspend fun join()
可以立即对取消做出反应。
请注意,这是比完整解决方案更多的解决方法,因为IO
调度程序中的一个线程在sleep()
到期之前仍将保持阻塞状态。