withTimeout()是否为协程构建器?是否会阻塞调用线程?
withTimeout 不属于协程构建器
协程核心构建器launch、async、runBlocking的核心特征是独立启动新协程,无需依附当前协程的顺序执行流,调用后即可返回协程句柄或直接阻塞线程等待执行结果。withTimeout是kotlinx.coroutines提供的作用域挂起扩展函数,不属于协程构建器范畴,其核心属性如下:
- 返回值:返回lambda执行块最后一行的执行结果,若超出指定时长块逻辑未完成,抛出
TimeoutCancellationException - 作用域:直接继承调用位置的协程作用域,作为当前协程的子协程遵循结构化并发规则:父协程会等待其执行完成才会推进后续逻辑,它自身被取消不会直接终止父协程
- 调度器:默认继承调用位置协程上下文绑定的调度器,支持传入自定义
CoroutineContext覆盖调度配置
示例代码执行逻辑说明
withTimeout不会阻塞调用线程,你之前的输出预期不符合协程的顺序执行规则,属于认知偏差。
协程内的代码默认遵循逐行顺序执行逻辑:只有当前行的逻辑执行完成(挂起函数恢复返回、普通函数返回),才会进入下一行代码的执行。
逐行拆解代码执行流:
- 进入
runBlocking启动的协程,首先执行第一个myScope.launch:该协程绑定Dispatchers.Default线程池,会被异步提交到Default线程池等待调度,启动动作本身是非阻塞的,提交完成后立刻返回,runBlocking协程继续向下执行。 - 执行到
withTimeout(3000):这是遵循结构化并发的挂起函数,会在当前runBlocking协程下启动子协程执行块内逻辑,执行到delay(2000)时,当前runBlocking协程挂起2秒——此时第二个myScope.launch(coroutine#2)对应的代码行还未被执行,协程根本没有被启动,自然不可能提前输出日志。 - 在withTimeout挂起的2秒窗口内,之前已经提交到Default线程池的coroutine#1获得CPU调度,打印
First coroutine launched。 - 2秒挂起时长结束,withTimeout块恢复执行,打印
withTimeout block executed,withTimeout执行完成返回,runBlocking协程恢复,继续向下执行。 - 此时才走到第二个
myScope.launch代码行,将coroutine#2提交到Default线程池,coroutine#2获得调度后打印Second coroutine launched,和实际运行输出完全匹配。
你之前的预期错误点在于:把withTimeout当成了异步启动协程的构建器,认为调用后会立刻返回执行下一行代码。实际上withTimeout是挂起函数,必须等自身块内逻辑执行完成(或超时抛出异常)才会返回,这是协程挂起的正常行为,和阻塞线程有本质区别:线程阻塞时底层线程会被占用空等,协程挂起时底层线程可以去执行其他就绪任务,只是当前执行流的后续逻辑需要等挂起恢复后才能推进。
如果要得到你预期的输出,需要在调用withTimeout之前就将两个myScope.launch全部启动提交,参考代码:
val myScope = CoroutineScope(Dispatchers.Default) runBlocking{ // 提前将两个协程提交到Default线程池 myScope.launch { //coroutine#1 Log.i(TAG, "First coroutine launched") } myScope.launch { //coroutine#2 Log.i(TAG, "Second coroutine launched") } // 所有协程提交完成后再执行超时逻辑 withTimeout(3000){ delay(2000) Log.i(TAG, "withTimeout block executed") } }
注意该场景下coroutine#1和coroutine#2的打印顺序并不绝对,取决于Dispatchers.Default线程池的实际调度情况。
内容的提问来源于stack exchange,提问作者ibrahim
相关产品推荐
相关产品推荐

