在以前的文章中,已经讲了如何启动协程、协程的做用域是如何组织和工做的以及各类协程构造器(builder)的特性。网络
本篇将讲解对协程的各类操做,包括挂起、取消、超时、切换上下文等。async
fun main() { runBlocking(Dispatchers.Default) { for (i in 0 .. 10) { println("aaaaa ${Thread.currentThread().name}") delay(1000) // 这是一个挂起函数 println("bbbbb ${Thread.currentThread().name}") } } }
delay就是一个挂起函数,挂起的意思是:非阻塞的暂停,与之对应的就是阻塞(的暂停)。好比线程的方法Thread.sleep就是一个阻塞的方法。关于阻塞仍是非阻塞,能够简单的理解为:函数
用图来表示线程阻塞的状况应该是这样:ui
而在协程中,非阻塞的状况应该是这样:spa
能够看到,线程的阻塞,那这个线程就真的不去作事情了,必须等到被唤醒了,才会继续执行,在被唤醒以前,这个线程资源能够说就被浪费了,若是我有新的任务,就必须在启动一个新的线程来执行。线程
可是协程上的挂起,它会去寻找有没有须要执行的代码块,若是有,就拿来跑,这样就能更高效的利用线程资源。若是挂起后,也没有发现任何能够执行的代码块,一样的也会进入阻塞状态,这一点和线程是同样的。code
在kotlin中,挂起函数只能在协程环境中使用。协程
等待一个协程执行完毕,和线程的API一致,使用join方法就能够了。blog
val job = launch { // .... } job.join()
若是须要返回值,也可使用async来启动协程,使用await方法来等待完成,并取得返回值数据。ip
val job = async { // .... } job.await()
await和join都是挂起函数。
协程应该被实现为能够被取消的,调用Job的cancel方法能够取消。可是,若是咱们写个while(true)的死循环怎么取消呢?
显然是取消不了的。
为了能让咱们的协程逻辑能被取消,就须要使用到协程的一个属性isActive。
假设咱们有一个协程是下载一个文件,咱们想让它能被取消。它多是这样:
val dlJob = launch { var isFinished = false while (!isFinished) { // download ... if (dlSize == totalSize) { isFinished = true } } }
这样的话,这个协程是没法被取消的,它没法被外侧所操控,咱们可使用isActive来改写一下。
val dlJob = launch { var isFinished = false while (!isFinished && isActive) { // 注意这里 // download ... if (dlSize == totalSize) { isFinished = true } } }
只须要这样,就能够实现取消逻辑了。
问题也就随之而来,像打开网络链接,读写文件,老是须要去执行一些close的逻辑才是符合规范的,若是协程被取消,就直接退出了,要如何才能回收打开的资源呢?
能够经过try{...}finally{...}进行回收资源,就像这样:
val dlJob = launch { try { var isFinished = false while (!isFinished && isActive) { // 注意这里 // download ... if (dlSize == totalSize) { isFinished = true } } } finally { // close something } }
当job被取消后,finally方法里面依然会在最后被执行,能够在这里进行一些回收的操做。
若是咱们指望一个协程最多只能执行多少时间,超过这个时间就要被取消的时候,就可使用超时逻辑,可使用withTimeout函数来实现。
runBlocking(Dispatchers.Default) { try { // 只容许协程执行最多500毫秒 val job = withTimeout(500) { try { println("working 1") delay(1000) println("working 2") } finally { println("finally, I will do something") } } println("job $job") // 没法被执行到 } catch (e: Throwable) { println("out coroutine $e") } }
若是超时了,则会抛异常,而且,这个函数与runBlocking是同样的,都会阻塞当前线程。上面的代码中,协程外的print不会被执行到。
若是不想抛异常,可使用另外一个超时函数withTimeoutOrNull。
runBlocking(Dispatchers.Default) { try { // 只容许协程执行最多500毫秒 val job = withTimeoutOrNull(500) { try { println("working 1") delay(1000) println("working 2") } finally { println("finally, I will do something") } } println("job $job") // 能够被执行到 } catch (e: Throwable) { println("out coroutine $e") } }
最终运行的结果是:
working 1 finally, I will do something job null
若是咱们指望协程的代码在不一样的线程中来回跳转,可使用withContext来实现。(emmmmm,这是什么场景的需求呢?)
newSingleThreadContext("Ctx1").use { ctx1 -> newSingleThreadContext("Ctx2").use { ctx2 -> runBlocking(ctx1) { log("Started in ctx1") withContext(ctx2) { log("Working in ctx2") } log("Back to ctx1") } } }
这里直接照搬文档中的示例代码,最后输出的结果为:
[Ctx1 @coroutine#1] Started in ctx1 [Ctx2 @coroutine#1] Working in ctx2 [Ctx1 @coroutine#1] Back to ctx1
以上就是操控协程的各类方法了。
挂起函数是协程中定义的概念,只能在协程中使用,挂起的含义是非阻塞的暂停,调度器会寻找须要运行的协程放到线程中去执行,若是找不到任何须要执行的协程,才会将线程阻塞。
协程是能够被取消的,任何系统提供的挂起函数内部都有取消的逻辑,若是本身的协程想要能够被取消,就必须经过isActive变量来编写逻辑。
取消后的协程老是会执行finally代码块,能够在这里进行一些资源回收的操做。
若是但愿控制协程的工做时长,可使用withTimeout来限制协程。
经过withContext函数来将逻辑切换到其余的线程上去。
以前的表格,就能够获得进一步的扩展了
若是你喜欢这篇文章,欢迎点赞评论打赏
更多干货内容,欢迎关注个人公众号:好奇码农君