试了挺久,到底还是对协程理解得不够深刻。
想来我又把公司坑了。。。
之前以为的写法(不生效):
withTimeout(timeout){ suspendCoroutine { do sth } }
后来看 stackoverflow 上的高赞改为:
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout) {
suspendCancellableCoroutine(block = block)
}
我寻思,这特么不是一样的么? 是不一样的,一个可Cancellable 一个不可。
private suspend inline fun testTimeOut() { withContext(Dispatchers.IO) { kotlin.runCatching { //not work /*withTimeout(2000L) { suspendCoroutine<String> { it.resume(time().get()) } }*/ //work withTimeout(2000L) { GlobalScope.launch { suspendCoroutine<String> { it.resume(time().get()) } }.join() } //work val d = async { suspendCoroutine<String> { it.resume(time().get()) } } withTimeout(2000) { d.await() } }.onFailure { it.printStackTrace() } } }
class time {
fun get(): String {
for (i in 0..1000) {
Thread.sleep(1000)
}
return ""
}
}
至于为什么不生效,我起初的理解:asyn 和 join 都返回了一个可取消可挂起的 future,这样做到可感知?看了下源码:
join 最终对应的是:
await 最终对应的是:
以上两个方法都在 JobSupport 类中。
普通 suspendCoroutine:
靠,根本看不出什么区别!!!关键在于Cancellable。于是我又偷偷的把【码上开学】看了一遍,扔老师说过了,协程没什么特别的,就是几个线程切来切去,非阻塞式挂起其实就是因为可以使用挂起函数配合一些如 withContext 进行按行切线程,从而不阻塞线程,不要想太复杂。
主要原因:
suspendCoroutine 将当前协程阻塞了,withTimeOut 无法执行完成,为什么会阻塞?因为线程并没有切换,单线程当然会阻塞,这又是一个很简单的问题!!!
而其他两种办法,都是在外层再套了一层协程,所以其阻塞的都是 async 或者GlobalScope 启动的新协程,这样 withTimeOut 才可以继续对其进行超时的判断。
(PS: withTimeOut 内部本身就是使用的 delay )
不信? 打个日志看一看:
第一种情况:
Log.d(TAG, "testTimeOut start: ${Thread.currentThread()}")
withTimeout(2000L) {
suspendCoroutine<String> {
Log.d(TAG, "testTimeOut: before resume ${Thread.currentThread()}")
it.resume(time().get())
}
}
第二种情况:
Log.d(TAG, "testTimeOut start: ${Thread.currentThread()}")
withTimeout(2000L) {
GlobalScope.launch {
suspendCoroutine<String> {
Log.d(TAG, "testTimeOut: before resume ${Thread.currentThread()}")
it.resume(time().get())
}
}.join()
}
第三种情况:
Log.d(TAG, "testTimeOut start: ${Thread.currentThread()}")
val d = async {
suspendCoroutine<String> {
Log.d(TAG, "testTimeOut: before resume ${Thread.currentThread()}")
it.resume(time().get())
}
}
withTimeout(2000) { d.await() }
豁然开朗,所以最后 挂起的协程 超时的正解是:
withTimeout(2000L) {
GlobalScope.launch {
suspendCoroutine<String> {
it.resume(time().get())
}
}.join()
}
以及:
val d = async { suspendCoroutine<String> { it.resume(time().get()) } } withTimeout(2000) { d.await() }
以及最好的办法是:
suspend inline fun <T> suspendCoroutineWithTimeout( timeout: Long, crossinline block: (Continuation<T>) -> Unit ) = withTimeout(timeout) { suspendCancellableCoroutine(block = block) }
写完已经凌晨1点半啦。。。学艺不精(菜)便是如此。
注:主要讨论withTimeOut
中是否是同步的情况,一般 suspendCoroutine 会配合异步代码块使用,这种情况是没问题的。后来基本没用过~
修正于:2022.4 感谢评论区小伙伴。
参考:
码上开学:https://kaixue.io/
kotlin-coroutine-withtimeout-does-not-cancel-when-using-withcontext-to-get-non-b
本站由以下主机服务商提供服务支持:
嘿嘿抛物线
stackoverflow 上的suspendCancellableCoroutine有效, suspendCoroutine因为是不可取消的,所以无效。withTimeout的方法注释中有写“cancellable suspending function”
Mosaic-C
哈哈哈,以前不知道啊
dylan
“suspendCoroutine 将当前协程阻塞了,withTimeOut 根本无法执行”,这句话不对!!!
// withTimeout(timeout){
// suspendCoroutine {
// do something
// }
// }
suspendCoroutine确实把当前协程阻塞了,从而使withTimeOut也被阻塞才对,withTimeOut肯定执行了,因为suspendCoroutine就是执行在withTimeOut的协程体内。
另外suspendCancellableCoroutine这用法应该是没问题的,不知道你为啥说这种写法不生效,你可以try { withTimeOut 代码块}catch{} finally,肯定是能捕获到超时异常的
Mosaic-C
你说的是对的,楼上也说过了,过去太久了没去修正,我修正下,感谢提醒。