Kotlin协程教程(3):操控协程

在以前的文章中,已经讲了如何启动协程、协程的做用域是如何组织和工做的以及各类协程构造器(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就是一个阻塞的方法。关于阻塞仍是非阻塞,能够简单的理解为:函数

  • 阻塞就是cpu不执行后面的代码,须要某种通知告诉线程继续执行。
  • 非阻塞就是cpu依然在执行线程的代码,非阻塞的暂停只是经过用户态的程序逻辑让代码块不执行而已。

用图来表示线程阻塞的状况应该是这样:ui

clipboard.png

而在协程中,非阻塞的状况应该是这样:spa

clipboard.png

能够看到,线程的阻塞,那这个线程就真的不去作事情了,必须等到被唤醒了,才会继续执行,在被唤醒以前,这个线程资源能够说就被浪费了,若是我有新的任务,就必须在启动一个新的线程来执行。线程

可是协程上的挂起,它会去寻找有没有须要执行的代码块,若是有,就拿来跑,这样就能更高效的利用线程资源。若是挂起后,也没有发现任何能够执行的代码块,一样的也会进入阻塞状态,这一点和线程是同样的。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函数来将逻辑切换到其余的线程上去。

以前的表格,就能够获得进一步的扩展了

clipboard.png

相关阅读

若是你喜欢这篇文章,欢迎点赞评论打赏
更多干货内容,欢迎关注个人公众号:好奇码农君

相关文章
相关标签/搜索