coroutineScope是一个suspend函数,创建一个新的协程作用域,并在该作用域内执行指定代码块,它并不启动协程。其存在的目的是进行符合结构化并发的并行分解(将长耗时任务拆分为并发的多个短耗时任务,并等待所有并发任务完成后再返回)。
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
coroutineScope是一个挂起函数,每一个被suspend修饰的方法都必须在另一个suspend函数或者Coroutine协程程序中进行调用。
CoroutineScope(Dispatchers.Main).launch{
coroutineScope {
//...
}
}
coroutineScope与runBlocking的区别在于:
runBlocking会阻塞当前线程,而coroutineScope不会阻塞所在的线程,它会挂起所在的协程直至其内部任务(包括子协程)执行完成。
private fun main() = runBlocking {
launch { // launch1
println("test2")
delay(1000) // delay1 // 挂起launch1
println("test4")
}
println("test1")
coroutineScope { // 第一次挂起runBlocking,直至内部逻辑完成
launch { // launch2
delay(2000) // delay2 // 挂起launch2
println("test5")
}
delay(500) // delay3 // 第二次挂起runBlocking
println("test3")
}
println("test6")
}
//System.out:
//test1
//test2
//test3
//test4
//test5
//test6
上述代码分析:
runBlocking在main线程创建并启动一个阻塞的协程,会阻塞main线程;
创建launch1子协程,由于创建协程是需要一些时间的,并且协程的创建是由特定的线程来完成,并非是main线程。所以在创建协程过程中会并行地执行后续代码。因此test1被输出。
执行到coroutineScope函数时,把runBlocking挂起,直到内部逻辑执行完成,所以最后输出test6。
然后创建launch2协程,创建过程中执行执行后续代码:delay3继续挂起runBlocking500ms(挂起函数中调用挂起函数)。
等到launch1创建完毕时,输出test2,delay1把launch1挂起1s。launch2创建完毕时,delay2把launch2挂起2s。
此时runBlocking、launch1、launch2都是被挂起状态。
等到500ms后runBlocking第二次挂起被恢复,输出test3,1s后launch1恢复,输出test4;2s后launch2被恢复,输出test5。
此时coroutineScope中的逻辑已经执行完成,恢复runBlocking的第一次挂起,test6被输出。
使用了runBlocking函数,导致主线程阻塞:
launch1和coroutineScope是并行的,launch1挂起时间delay1 == 1s;coroutineScope内部launch2和delay3是并行的,launch2挂起时间delay2 == 2s,delay3 == 500ms,故coroutineScope挂起时间 == 2s,coroutineScope挂起时间 > launch1挂起时间,所以runBlocking函数的阻塞时间 == 2s。