前言
在上一篇中,对Kotlin协程进行了初步认识。在这篇中,将会讲解Kotlin对应取消超时组合挂起函数!
话不多说,直接开始!
1、挂起函数
先看上一篇的例子:
fun main() = runBlocking<Unit> {// this : CoroutineScope
launch {
delay(1000L)
println("Kotlin!")
}
println("Hello,")
}
这是上一篇的例子,这里我们看到在launch
闭包里,填写了我们实现的逻辑,但是在真实使用中,大多数都是将对应逻辑放在方法里,然后调用对应方法。
因此,我们按照真实使用情况继续试试看效果:
fun main() = runBlocking<Unit> {
launch { doKotlin() }
println("Hello,")
}
fun doKotlin(){
delay(1000L) //这句代码报错
println("Kotlin!")
}
我们发现这样写居然报错了!难道不支持这样写么?上面有提示,点一下看看!
fun main() = runBlocking<Unit> {
launch { doKotlin() }
println("Hello,")
}
// 这是你的第⼀个挂起函数
suspend fun doKotlin(){
delay(1000L)
println("Kotlin!")
}
现在代码没报错了,运行下看看效果:
Hello,
Kotlin!
OK,我们看到方法前加了一个额外的suspend
关键字,这样的方法才能在协程里面内部调用!
那如果说,非协程调用这方法会怎样呢?
suspend fun doKotlin(){
delay(1000L)
println("Kotlin!")
}
fun main(){
doKotlin() //这句话报错,
}
这样写还是报错!编译器提示我们需要将main
函数也额外加上suspend
关键字!
到这,我们应该能清晰的认知到,当遇到带有suspend
关键字的方法时,要第一时间想到,这个方法仅仅只能在协程里面调用的!
2、验证协程轻量性
在上一篇提到过:协程实际上是⼀个轻量级的线程,可以挂起并稍后恢复。
那么在这就来验证一下协程轻量性具体在哪!
fun main() = runBlocking<Unit> {
repeat(100000){
launch {
delay(5000)
print(".")
}
}
}
这里使用了repeat
关键字,来看看它是什么意思:
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
通过这段代码,能够清楚的认知到,里面就是一个循环,循环内容就是对应闭包里面的内容!
也就是说:这里通过repeat(100000)
启动了十万个协程,让每个协程在5秒后统一打印这个小数点。
来看看运行效果:
如图所示
当运行等待5秒后,这些小数点是在一瞬间统一出来的。
如果说用java线程来实现,很有可能直接产生内存不足的错误!
所以说,协程是一个轻量性线程!
不仅如此,一个全局协程就像是一个守护线程:在 GlobalScope 中启动的活动协程并不会使进程保活。
3、全局协程像守护线程
fun main() = runBlocking<Unit> {
GlobalScope.launch {
repeat(1000){ i ->
println("I'm sleeping $i")
delay(10L)
}
}
delay(130L)
println("over ")
}
现在我们看到:通过GlobalScope.launch
创建了对应的活动协程,里面每隔10L
将打印对应的1000次循环输出。在活动协程外面只挂起了130L
,最后执行打印!
来看看运行效果:
I'm sleeping 0
I'm sleeping 1
I'm sleeping 2
I'm sleeping 3
I'm sleeping 4
I'm sleeping 5
I'm sleeping 6
I'm sleeping 7
I'm sleeping 8
I'm sleeping 9
I'm sleeping 10
I'm sleeping 11
over
我们看到:当主线程完毕时,在 GlobalScope
中启动的活动协程并不会使进程保活。
那如果说将GlobalScope
关键字删除将会是怎样呢?
fun main() = runBlocking<Unit> {
launch {
repeat(1000){ i ->
println("I'm sleeping $i")
delay(10L)
}
}
delay(130L)
println("over ")
}
运行效果
如图所示
这里我们看到:当主线程执行完毕时,非全局协程依然会继续执行,直到协程执行结束,整个主线程才会完毕。
那如果说,我想要非全局协程在主线程执行完毕时,也强制结束该怎么办呢?
4、取消协程的执行
fun main() = runBlocking<Unit> {
val job = launch {
repeat(1000){ i ->
println("job:I'm sleeping $i")
delay(10L)
}
}
delay(130L)
println("main: I'm tired of waiting!")
job.cancel()
job.join()
println("main:Now I can quit!")
}
运行效果
job:I'm sleeping 0
job:I'm sleeping 1
job:I'm sleeping 2
job:I'm sleeping 3
job:I'm sleeping 4
job:I'm sleeping 5
job:I'm sleeping 6
job:I'm sleeping 7
job:I'm sleeping 8
job:I'm sleeping 9
job:I'm sleeping 10
job:I'm sleeping 11
main: I'm tired of waiting!
main:Now I can quit!
我们这里看到:
- 在代码里加了
job.cancel()
和job.join()
- 顾名思义
job.cancel()
肯定是对应取消操作 - 而
job.join()
在上一篇中提过,这个是等待协程执行完毕 - 也就是说,这里先是取消对应的协程,然后等待协程所有操作全部执行完毕(包括取消)执行完毕时
- 就可以执行最后那句
main:Now I can quit!
打印了 - 当然官方额外提供了
job.cancelAndJoin()
方法,将两者合成一个方法
fun main() = runBlocking<Unit> {
val job = launch {
repeat(1000){ i ->
println("job:I'm sleeping $i")
delay(10L)
}
}
delay(130L)
println("main: I'm tired of waiting!")
// job.cancel()
// job.join()
job.cancelAndJoin()
println("main:Now I can quit!")
}
运行结果和上面一致!
那是不是所有的协程都能像刚刚那样还没执行完就立马取消掉?
当然肯定不是的!
5、协程的取消是协作的
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
var nextPrintTime = startTime
var i = 0
//这里没有挂起函数
while(i < 50){ // ⼀个执⾏计算的循环,只是为了占⽤ CPU
if(System.currentTimeMillis() >= nextPrintTime){
println("job:I'm sleeping ${i++} ...")
nextPrintTime += 10L
//delay(1L) //分析1
}
}
}
delay(130L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
运行效果
job:I'm sleeping 0 ...
...略
...略
job:I'm sleeping 49 ...
main:I'm tired of waiting!
main:Now I can quit.
这里没有使用delay
挂起函数,发现主线程只能等待协程执行完了后才能关闭。
那这时取消分析1的注释试试看效果:
job:I'm sleeping 0 ...
...略
job:I'm sleeping 15 ...
main:I'm tired of waiting!
main:Now I can quit.
这里可以看到当取消分析1的注释,即使挂起1毫秒,取消时也会像上一个例子一样强制取消;
所以说:当没有使用delay
挂起函数时,如果协程正在执⾏计算任务,并且没有检查取消的话,那么它是不能被取消的。
这里提到,没有检查取消。 那看看检查取消是怎么使用的呢?
6、检查取消
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
//isActive 是⼀个可以被使⽤在 CoroutineScope 中的扩展属性
while(i < 50 && isActive){
if(System.currentTimeMillis() >= nextPrintTime){
println("job:I'm sleeping ${i++} ...")
nextPrintTime += 10L
}
}
}
delay(130L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
可以看到这里launch()
里面多了Dispatchers.Default
,随后在while
多了isActive
判断。
先来看看运行效果
运行效果
job:I'm sleeping 0 ...
...略
...略
job:I'm sleeping 15 ...
main:I'm tired of waiting! //这句打印,可能在16 后面,可能在16 前面
job:I'm sleeping 16 ...
main:Now I can quit.
从这运行效果可以看到:
- 当执行了
job.cancelAndJoin()
代码时,对应通过launch(Dispatchers.Default)
创建的协程里面isActive
属性将为false
, - 这样主线程结束时,对应无挂起函数的非主协程也能结束运行!
结束语
好了,本篇到这里差不多结束了!相信你对协程挂起以及取消操作有了一定的认知。在下一篇中,将会继续深一步讲解协程!