Kotlin协程:async与GlobalScope.async的超时行为差异探究
Kotlin协程withTimeout不触发超时的原因分析
问题描述
需要用超时机制包装Java回调函数,防止回调永不被调用,但首次尝试的代码未按预期在500ms后抛出TimeoutCancellationException,替换为GlobalScope.async后超时正常生效。代码如下:
fun main() = runBlocking { withTimeout(500) { async { notCalledCallback() }.await() } Unit } private suspend fun notCalledCallback() = suspendCoroutine<Boolean> { cont -> startScanning(object : SomeCallback { override fun done() { cont.resume(true) } }) } fun startScanning(callBack: SomeCallback) { // callback may never be invoked // callBack.done() } interface SomeCallback { fun done() }
核心原因:协程作用域与取消传播的差异
1. 当前作用域async的行为
- 不带
GlobalScope的async会创建当前withTimeout作用域的子协程,两者属于父子Job关系。 - 当
withTimeout超时后,会触发作用域内所有协程的取消:包括执行await()的协程,以及async创建的子协程。 - 但
notCalledCallback中使用的suspendCoroutine不处理取消逻辑:既不会主动检查协程的取消状态,也不会在协程被取消时调用cont.resumeWithException来结束挂起。 - 此时
await()会一直等待子协程完成,但子协程因未处理取消,会一直处于活跃状态,导致await()永久挂起,withTimeout的超时异常无法抛出。
2. GlobalScope.async的行为
GlobalScope.async创建的是顶级协程,不属于withTimeout的作用域,两者没有父子Job关联。- 当
withTimeout超时后,只会取消自身的协程,不会影响这个顶级协程。 - 此时执行
await()的协程被取消,await()会立即响应取消并抛出CancellationException,withTimeout会将其包装为TimeoutCancellationException,符合预期触发超时。
正确的解决方案(可选)
不需要依赖GlobalScope,只需将suspendCoroutine替换为suspendCancellableCoroutine,并处理取消逻辑:
private suspend fun notCalledCallback() = suspendCancellableCoroutine<Boolean> { cont -> startScanning(object : SomeCallback { override fun done() { cont.resume(true) } }) // 当协程被取消时,可在这里做清理操作(比如停止扫描) cont.invokeOnCancellation { // stopScanning() } }
这样,当withTimeout超时取消协程时,suspendCancellableCoroutine会自动触发取消逻辑,await()会立即响应取消,超时异常正常抛出。
内容的提问来源于stack exchange,提问作者mtw
相关产品推荐
相关产品推荐

