2.1 协程的阻塞与非阻塞
2.1.1 同步一定阻塞?
同步和阻塞这两个概念在我们学习的时候很容易造成困惑,因为这两个经常放在一起,导致可能存在部分同学认为同步即阻塞,阻塞即同步的错误观点。 我们看一下下面的代码:
fun main(){
val netInfo:NetInfo = getInfoByNetwork()
val res:Result = processNetInfo(netInfo)
}
上面的代码是我们常见的同步方式,在等待第一步执行完成,拿到 netInfo 后,才能执行第二步,而在这里如果第一步都没有大量的耗时操作,那么这个方法很快就会执行,也就不会阻塞第二步的执行。这里其实就没有阻塞一说,同样的,换成异步方式,在异步的回调中如果存在这耗时操作,那么异步方法内部也是进入阻塞状态,等待完全执行后才会返回。所以 同步异步是处理的方式,阻塞和非阻塞指状态。
2.1.2 阻塞与非阻塞
在上一篇我们对协程有了基本的了解,在比较协程和线程的时候,使用了runBlocking函数。当时并没有说明 launch 和 runBlocking的区别。现在我们详细讲一下这两者的异同,我们以上次的代码作为示例
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking { //阻塞主线程(后续介绍)
repeat(10000) {
launch {
println("Hello")
}
}
}
}
首先这两者都是可以创建一个协程的,区别在于:
-
runBlocking 是创建主协程,为最高级的协程。
-
launch 创建的协程是可以在 runBlocking 中运行的协程,上述的代码可以理解为 在 runBlocking 协程中创建了一个 launch 的子协程。
注意: runBlocking 创建的协程是阻塞当前线程的,会等待所有的子携程全部执行完成。也就是说 **runBlocking **是一个阻塞函数。launch 是一个非阻塞函数
2.2 协程的生命周期
上面的例子里面,我们使用主协程创建子协程的方式实现了一个阻塞和非阻塞的例子,我们接着看下面的例子
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking<Unit> {
GlobalScope.launch {
delay(1000L)
println("world")
}
delay(2000L)
println("hello")
}
在上面的代码中,我们使用 delay方法延迟两秒来保活,确保 GlobalScope.launch{}的执行,如果此处执行的是 IO 操作,这种情况下我们并不知道 IO 操作到底耗时多久,所以通常我们的代码如下
launch{
ioFun()
}
为了能够让这样场景下,协程能够得到执行,Kotlin 给了一个方法 join,这个方法能够让程序在协程执行完毕之前一直保活,使用方式如下:
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val job:Job = launch {
ioFun()
}
println("main fun")
job.join() //使用 join
}
suspend fun ioFun(){
delay(2000L)
println(" io fun")
}
我们这里使用delay方法模拟 IO 操作的延时,然后输出,然后通过 job.join()
保证了程序会一直等待协程的结束。这里的等待是非阻塞的,并不会将当前的线程挂起。我们还会注意到在ioFun
函数前面有个 suspend修饰,这里修饰表示 ioFun是一个挂起函数,可以被协程内部调用或者其他挂起函数调用,不能在普通方法内部调用。
有等待协程执行结束的方法肯定也存在取消协程的方法,没错,协程的取消可以调用cancel函数来取消协程的执行
我们看下面的示例:
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")
}
这里我们每隔 500ms,循环打印 1000 次,同时主协程延迟一段时间,输出的结果如下:
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
我们看到,在循环打印三次之后就停止打印了。协程也就被取消了。