You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:10:28