为何launch函数的block参数同时标记suspend与CoroutineScope扩展?
根据官方文档,launch函数的签名如下:
fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job
其中block参数同时被标记为suspend函数以及CoroutineScope的扩展函数。
Roman Elizarov曾在文章中提到:
若需启动一个在函数返回后仍持续运行的协程,请将函数设为CoroutineScope的扩展或传入scope: CoroutineScope参数以明确意图,请勿将此类函数标记为suspend
那么问题来了:为何协程构建器函数接收的lambda不符合“仅标记为suspend或作为CoroutineScope扩展”这一规则?
这其实是协程构建器的特殊设计需求,和普通业务函数的规则场景完全不同,核心原因有两点:
让Lambda拥有协程作用域的能力
作为CoroutineScope的扩展函数,lambda内部可以直接调用launch、async等其他协程构建器,不需要额外传递作用域参数——这是为了让代码更简洁自然,比如我们能直接写launch { launch { ... } },而不是繁琐地指定外层作用域。允许Lambda执行挂起操作
标记为suspend是因为协程体本身就是用来执行异步挂起逻辑的,比如调用delay()、网络请求这类挂起函数,如果不标记suspend,lambda里根本没法编写这些异步逻辑,完全违背了协程的设计初衷。
而Roman提到的“二选一”规则,是针对普通业务函数的:比如你写一个业务函数,如果它要启动后台协程(函数返回后协程仍在运行),那应该把它设为CoroutineScope的扩展,而非标记为suspend——因为suspend函数的语义是“挂起直到任务完成”,如果一个suspend函数启动后台协程后直接返回,会让调用者误以为函数已执行完毕,实则后台还有任务在运行,这就违背了suspend的语义约定。
但协程构建器的lambda是协程的执行体,它的语义就是“在协程作用域内执行挂起逻辑”,这两个特性都是必须的,所以才会同时具备。
内容的提问来源于stack exchange,提问作者niting112

