抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

Kotlin 协程:suspendCoroutine withTimeout 无效问题,我也把它皮扒了!!!

试了挺久,到底还是对协程理解得不够深刻。

想来我又把公司坑了。。。

之前以为的写法(不生效):

withTimeout(timeout){
    suspendCoroutine { 
        do sth
    }
}

后来看 stackoverflow 上的高赞改为(依旧不生效):

suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout) {
suspendCancellableCoroutine(block = block)
}

我寻思,这特么不是一样的么?直到一系列的尝试后。。。

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,这样做到可感知?(一个 while true 的回调)但看了下源码:

join 最终对应的是:

await 最终对应的是:

以上两个方法都在 JobSupport 类中。

普通 suspendCoroutine:

靠,根本看不出什么区别!!!于是我又偷偷的把【码上开学】看了一遍,扔老师说过了,协程没什么特别的,就是几个线程切来切去,非阻塞式挂起其实就是因为可以使用挂起函数配合一些如 withContext 进行按行切线程,从而不阻塞线程,不要想太复杂。想啊想,一个是基于 Job,一个是基于当前协程。再一想,噢。

主要原因:

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() }

写完已经凌晨1点半啦。。。学艺不精(菜)便是如此。

注:主要讨论withTimeOut中是否是同步的情况,一般suspendCoroutine会配合异步代码块使用,这种情况是没问题的。

参考:

码上开学:https://kaixue.io/

kotlin-coroutine-withtimeout-does-not-cancel-when-using-withcontext-to-get-non-b

kotlin-coroutines-with-time


0条评论

发表评论