协程(Coroutine)是一种程序组件,它可以在执行过程中挂起和恢复,而不需要显式地创建和管理线程。协程由三部分组成:协程体(Coroutine Body),协程构造器(Coroutine Constructor)和协程挂起函数(Suspend Function)。
协程 CoroutineScope (协程作用域) 的上下文中通过 launch、async 等构造器来启动。GlobalScope ,即全局作用域内启动了一个新的协程,这意味这该协程的生命周期只受整个应用程序的生命周期的限制,即只要整个应用程序还在运行中,只要协程的任务还未结束,该协程就可以一直运行。 // launch 代码块默认运行于线程池中
//在后台启动一个新协程
GlobalScope.launch {
delay(1000)//非阻塞式延迟
println("World!")
}
println("Hello ")
runBlocking函数同样会创建一个协程的作用域,但是它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。需要注意的是,runBlocking函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能上的问题。// runblocking 运行 main 线程
除了使用官方的几个协程构建器之外,还可以使用 coroutineScope 来声明自己的作用域。 coroutineScope 用于创建一个协程作用域,直到所有启动的子协程都完成后才结束。
RunBlocking 和 coroutineScope 看起来很像,因为它们都是需要等待其它内部所有相同作用域的子协程结束后才会结束自己。两者的主要区别在于 runBlocking 方法会阻塞当前线程,而 coroutineScope 只是挂起并释放底层底层线程以供其他协程使用。由于这个差别,所以 runBlocking 是一个普通函数,而 coroutineScope 是一个挂起函数。
runBlocking {
//非阻塞 GlobalScope
launch {
delay(200)
println("Task from runBlocking")
}
//非阻塞
coroutineScope {
launch {
delay(500)
println("Task from nested launch")
}
delay(100)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
suspend 挂起函数可以想常规函数一样在协程中使用,但是它们的额外特性是:可以依次使用其他挂起函数(如delay函数)来使协程挂起。
suspend fun doWorld() {
delay(1000)
println("World!")
}
suspend关键字只能将一个函数声明成挂起函数,是无法给它提供协程作用域的。比如你现在尝试在doWorld()函数中调用launch函数,一定是无法调用成功的,因为launch函数要求必须在协程作用域当中才能调用
这个问题可以借助coroutineScope函数来解决。coroutineScope函数也是一个挂起函数,因此可以在任何其他挂起函数中调用。它的特点是会继承外部的协程的作用域并创建一个子协程,借助这个特性,我们就可以给任意挂起函数提供协程作用域了。
suspend fun doWorld() = coroutineScope{
launch {
println(".")
delay(1000)
}
}
总结看上去coroutineScope函数和runBlocking函数的作用是有点类似的,但是coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不会造成任何性能上的问题的。而runBlocking函数由于会挂起外部线程,如果你恰好又在主线程中当中调用它的话,那么就有可能会导致界面卡死的情况,所以不太推荐在实际项目中使用。
GlobalScope.launch、runBlocking、launch、coroutineScope这几种作用域构建器,它们都可以用于创建一个新的协程作用域。不过GlobalScope.launch和runBlocking函数是可以在任意地方调用的,coroutineScope函数可以在协程作用域或挂起函数中调用,而launch函数只能在协程作用域中调用。
runBlocking由于会阻塞线程,因此只建议在测试环境下使用。而GlobalScope.launch由于每次创建的都是顶层协程,一般也不太建议使用,除非你非常明确就是要创建顶层协程。因此,GlobalScope.launch这种协程作用域构建器,在实际项目中也是不太常用的。下面我来演示一下实际项目中比较常用的写法:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
//处理具体逻辑
}
job.cancel()
可以看到,我们先创建了一个Job对象,然后把它传入CoroutineScope()函数当中,注意这里的CoroutineScope()是个函数,虽然它的命名更像是一个类。CoroutineScope()函数会返回一个CoroutineScope对象,这种语法结构的设计更像是我们创建了一个CoroutineScope的实例,可能也是Kotlin有意为之的。有了CoroutineScope对象之后,就可以随时调用它的launch函数来创建一个协程了。
现在所有调用CoroutineScope的launch函数所创建的协程,都会被关联在Job对象的作用域下面。这样只需要调用一次cancel()方法,就可以将同一作用域内的所有协程全部取消,从而大大降低了协程管理的成本。