android 协程基础(二)

协程构建器

launch和async构建器

launch

返回一个Job并且不会附带任何结果值

async

返回一个Deferred,Deferred是一个Job,可以使用await()在一个延期的值上得到它的最终结果
eg:通过await()来获取结果

 GlobalScope.launch{
            val job1 = launch {
                delay(200)
                println("job1")
            }
            println("job 测试顺序1")

            val job2 = async {
                delay(300)
                println("job2")
                "job2 result"
            }
            println("job 测试顺序2")

            println(job2.await())

            println("job 测试顺序3")

        }
        println("job 测试顺序0")
job 测试顺序0
job 测试顺序1
job 测试顺序2
job1
job2
job2 result
job 测试顺序3

join

join()方法可以在一个作业中中等待另一个作业结束后再进行其他操作,将上面代码改变下:

 GlobalScope.launch{
            val job1 = launch {
                delay(1000)
                println("job1")
            }
           
            println("job 测试顺序1")

            val job2 = async {
                delay(100)
                println("job2")
                "job2 result"
            }
            println("job 测试顺序2")

            println(job2.await())

            println("job 测试顺序3")

        }
        println("job 测试顺序0")

结果(未使用join方法):发现挂起时间短的先执行

2021-08-15 15:45:25.547 5604-5604/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:45:25.548 5604-5638/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:45:25.550 5604-5638/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job2
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job2 result
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job 测试顺序3
2021-08-15 15:45:26.557 5604-5637/com.hujin.wan I/System.out: job1

如果需要job按照顺序执行的话,比如先执行job1,再执行job2或者job3:
eg:job1执行join方法

 GlobalScope.launch{
            val job1 = launch {
                delay(1000)
                println("job1")
            }
            job1.join()
            println("job 测试顺序1")

            val job2 = async {
                delay(100)
                println("job2")
                "job2 result"
            }

            val job3 = launch {
                delay(100)
                println("job3")
            }
            println("job 测试顺序2")

            println(job2.await())

            println("job 测试顺序3")

        }
        println("job 测试顺序0")

结果:job1先执行,再执行job2和job3

2021-08-15 15:49:00.647 5730-5730/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:49:01.653 5730-5764/com.hujin.wan I/System.out: job1
2021-08-15 15:49:01.655 5730-5764/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:49:01.655 5730-5764/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:49:01.760 5730-5764/com.hujin.wan I/System.out: job3
2021-08-15 15:49:01.760 5730-5763/com.hujin.wan I/System.out: job2
2021-08-15 15:49:01.761 5730-5763/com.hujin.wan I/System.out: job2 result
2021-08-15 15:49:01.761 5730-5763/com.hujin.wan I/System.out: job 测试顺序3

eg:使用async/await来代替join,到达同样的效果

 GlobalScope.launch{
            val job1 = async {
                delay(1000)
                println("job1")
            }
            job1.await()
            println("job 测试顺序1")

            val job2 = async {
                delay(100)
                println("job2")
                "job2 result"
            }

            val job3 = launch {
                delay(100)
                println("job3")
            }
            println("job 测试顺序2")

            println(job2.await())

            println("job 测试顺序3")

        }
        println("job 测试顺序0")

结果:到达同样的效果

2021-08-15 15:54:10.557 5913-5913/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:54:11.565 5913-5946/com.hujin.wan I/System.out: job1
2021-08-15 15:54:11.566 5913-5946/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:54:11.569 5913-5946/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job3
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job2
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job2 result
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job 测试顺序3

组合并发

先看一场景,2个结果的相加:

 private fun testCombination() {
        GlobalScope.launch {
            val time = measureTimeMillis {
                val one = doOne()
                val two = doTwo()
                println("the result : ${one + two}")
            }
            println("the pay time $time")
        }
    }


    suspend fun doOne():Int{
        delay(1000)
        return 15
    }

    suspend fun doTwo():Int{
        delay(1000)
        return 20
    }
2021-08-15 16:52:35.496 6575-6604/com.hujin.wan I/System.out: the result : 35
2021-08-15 16:52:35.496 6575-6604/com.hujin.wan I/System.out: the pay time 2008

你会发现这两个结果内部是同步的,消耗时间是两个方法总时间,改造下:

 private fun testCombination() {
        GlobalScope.launch {
            val time = measureTimeMillis {
                val one = async { doOne() }
                val two = async { doOne() }
                println("the result : ${one.await() + two.await()}")
            }
            println("the pay time $time")
        }
    }

结果:

2021-08-15 16:57:07.113 6725-6758/com.hujin.wan I/System.out: the result : 30
2021-08-15 16:57:07.113 6725-6758/com.hujin.wan I/System.out: the pay time 1016

协程的启动模式

先看下launch的代码:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

在我们协程启动的时候,可以设置一个启动模式,默认是CoroutineStart.DEFAULT
启动模式是指定协程在启动后是怎样的一个行为,一共有四种启动模式:

DEFAULT

协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消的响应状态

 private  fun testStartMode(){
        runBlocking {
            val job = launch(start = CoroutineStart.DEFAULT) {
                println("job 被挂起")
                delay(10000)
                //下面这句不会被执行
                println("job finished")
            }
            delay(1000)
            job.cancel()
        }
    }
  private  fun testStartMode(){
        val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
            //下面这句可能不会被执行
            println("job finished")
        }
        job.cancel()
        println("job cancel")
    }

上面两种案例是不同的,一个是协程的执行期间被取消,第二个是调度前被取消的,可对照下面ATOMIC

ATOMIC

协程创建后,立即开始调度(不代表立即执行),协程执行到第一个挂起点之前不会响应取消(原子性)

 private  fun testStartMode(){
        val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
            //下面这句是会执行的
            println("job finished")
        }
        job.cancel()
        println("job cancel")
    }

结果:

job cancel
job finished"
    private  fun testStartMode(){
        val job = GlobalScope.launch(Dispatchers.Default,start = CoroutineStart.ATOMIC) {
          
            println("job start")
            delay(10)
            //这句不会执行
            println("job finished")

        }
        job.cancel()
        println("job cancel")
    }

结果:

job cancel
job start

LAZY

只有在需要的时候(调用协程start、join、await等)才会开始调度,如果调度前被取消,就不会执行,而是直接进入异常结束状态.一般先是用来定义好的。
eg:需要调用join才会执行协程体的内容,调用cancelAndJoin是无法执行的

    private  fun testStartMode(){
        runBlocking {
            val job = GlobalScope.launch(Dispatchers.Default,start = CoroutineStart.LAZY) {
                //下面这句可能不会被执行
                println("job start")
//                delay(100)
//                println("job finished")

            }
            job.join()
            println("job cancel")
        }

    }

UNDISPATCHED

协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点.

协程的作用域构建器

runBlocking与coroutineScope

  • runBlocking是常规函数,而coroutineScope是挂起函数
  • 都会等待其协程体以及所有子协程结束,区别在于runBlocking方法会阻塞当前线程来等待,而coroutineScope只是挂起,会释放底层线程去干其他的

runBlocking 的例子,会阻塞等待延迟结束

 private  fun testStartCoroutine(){
        runBlocking {
            delay(1000)
            Log.e("TAG","runBlocking[当前线程为:${Thread.currentThread().name}]")

        }
        Log.e("TAG","1.runBlocking[当前线程为:${Thread.currentThread().name}]")

    }
2021-08-29 16:29:50.756 22304-22304/com.hujin.wan E/TAG: runBlocking[当前线程为:main]
2021-08-29 16:29:50.757 22304-22304/com.hujin.wan E/TAG: 1.runBlocking[当前线程为:main]

CoroutineScope 的例子,会挂起执行后面逻辑

 private  fun testStartCoroutine(){
        CoroutineScope(Dispatchers.Main).launch {
            delay(1000)
            Log.e("TAG","CoroutineScope[当前线程为:${Thread.currentThread().name}]")

        }
        Log.e("TAG","1.CoroutineScope[当前线程为:${Thread.currentThread().name}]")

    }

2021-08-29 16:40:16.385 22504-22504/com.hujin.wan E/TAG: 1.CoroutineScope[当前线程为:main]
2021-08-29 16:40:18.029 22504-22504/com.hujin.wan E/TAG: CoroutineScope[当前线程为:main]

coroutineScope与supervisorScope

  • coroutineScope 一个协程失败了,所以其他兄弟协程也会被取消。内部的异常会向上传播,子协程未捕获的异常会向上传递给父协程,任何一个子协程异常退出,会导致整体的退出。
  • supervisorScope 一个协程失败了,不会影响其他兄弟协程。内部的异常不会向上传播。
 runBlocking {
                supervisorScope {
                    val child1 = launch {
                        delay(400)
                        println("Child 1 is finished")
                    }
                    val child2 = launch {
                        println("Child 2 is sleeping")
                        delay(200)
                        println("Child 2 throws an exception")
                        throw AssertionError()
                    }
                }

            }

Job

Job对象

对于每一个创建的协程(通过launch或者async),会返回一个Job实例,是协程的唯一标示,并负责管理协程的生命周期。
一个任务可以包含一系列状态:新创建(New)、活跃(Active)、完成中(Completing)、已完成(Completed)、取消中(Cancelling)和已取消(Cancelled)。可以通过Job来访问这些熟悉。

  private fun testJob() {
        val job = GlobalScope.launch {

        }
        val active = job.isActive
        val cancelled = job.isCancelled
        val completed = job.isCompleted
    }

Job的生命周期

在这里插入图片描述
如果协程处于活跃状态,在运行出错或者调用job.cancel()都会将当前任务置为取消中(Cancelling)状态(isActive = false,isCancelled = true)。当所以的子协程都完成后,协程会进入已取消(Cancelled)状态,此时isCompleted = true。

协程的取消

几个基本情况:

  • 取消作用域会取消它的子协程
  • 被取消的子协程并不会影响其余兄弟协程
  • 协程通过抛出一个特殊的异常CancellationException来处理取消操作。(不会真的抛错)
  • 所以coroutines中的挂起函数都是可以取消的。eg:withContext、delay等

子协程独立情景

 runBlocking {
            val request = launch {
                //job1 使用 GlobalScope 来启动
                GlobalScope.launch {
                    println("tag-test job1 execute   thread:"+ Thread.currentThread().name)
                    delay(1000)
                    println("tag-test job1 is finished   thread:"+ Thread.currentThread().name)
                }

                //job2 继承了父协程的上下文
                launch {
                    delay(100)
                    println("tag-test job2 execute   thread:"+ Thread.currentThread().name)
                    delay(1000)
                    println("tag-test job2 finished   thread:"+ Thread.currentThread().name)

                }
            }
            delay(500)
            request.cancel()
            delay(1000)
            println("tag-test all is  finished   thread:"+ Thread.currentThread().name)
        }

运行结果:job1是被独立出去了,即使父协程已经取消了。

job1 execute   thread:DefaultDispatcher-worker-4
job2 execute   thread:main
job1 is finished   thread:DefaultDispatcher-worker-4
all is  finished   thread:main

作用域取消

runBlocking {
            val request = launch {
                repeat(3) { i ->
                    launch {
                        println("coroutine $i is start")
                        delay((i + 1) * 200L)
                        println("coroutine $i is done")
                    }
                }
            }
            delay(300)
            request.cancel()
            println("coroutine request is done")
        }

结果:作用域取消了子协程

coroutine 0 is start
coroutine 1 is start
coroutine 2 is start
coroutine 0 is done
coroutine request is done

cpu密集型取消

先看一种,cpu密集型取消不成功的情况,直接调用job.cancle()方法不会取消:

 runBlocking {
            val startTime = System.currentTimeMillis()
            val job = launch(Dispatchers.Default) {
                var nextPrintTime = startTime
                var i = 0
                while (i < 5){
                    if(System.currentTimeMillis() >= nextPrintTime){
                        println("job: i am sleeping ${i++}...")
                        nextPrintTime+=500
                    }
                }
            }
            delay(1000)
            println("main : i am tried of waiting!")
            job.cancelAndJoin()
            println("main: now i can quit")
        }

测试结果:无法被取消

job: i am sleeping 0...
job: i am sleeping 1...
job:i am sleeping 2...
i am tried of waiting!
job: i am sleeping 3...
job: i am sleeping 4...
main: now i can quit

正确的退出方式,应该通过job的生命周期来退出cpu的计算逻辑,在调用cancle方法后,isActive会置为false,所以改动如下:

runBlocking {
            val startTime = System.currentTimeMillis()
            val job = launch(Dispatchers.Default) {
                var nextPrintTime = startTime
                var i = 0
                while (i < 5 && isActive){
                    if(System.currentTimeMillis() >= nextPrintTime){
                        println("job: i am sleeping ${i++}...")
                        nextPrintTime+=500
                    }
                }
            }
            delay(1000)
            println("main : i am tried of waiting!")
            job.cancelAndJoin()
            println("main: now i can quit")
        }

或者利用ensureActive()方法,效果一样的。

 runBlocking {
            val startTime = System.currentTimeMillis()
            val job = launch(Dispatchers.Default) {
                var nextPrintTime = startTime
                var i = 0
                while (i < 5 ){
                    ensureActive()
                    if(System.currentTimeMillis() >= nextPrintTime){
                        println("job: i am sleeping ${i++}...")
                        nextPrintTime+=500
                    }
                }
            }
            delay(1000)
            println("main : i am tried of waiting!")
            job.cancelAndJoin()
            println("main: now i can quit")
        }

结果:成功取消,退出while循环

job: i am sleeping 0...
job: i am sleeping 1...
job:i am sleeping 2...
i am tried of waiting!
main: now i can quit

还可以调用yield()方法,这个会让出cpu的调度。

在finally中释放资源

在协程被取消的时候抛出CancellationException 异常,可以在结构体里添加try{...}finally{...}中执行终结动作。

val job = launch {
                try {
                    repeat(1000) { i ->
                        println("job : i am sleeping $i")
                        delay(400)
                    }
                } finally {
                    println("job: i am running finally")
                }

            }
            delay(2000L)
            println("main: i am tired of waiting!")
            job.cancelAndJoin()
            println("main: now i can quit")

测试结果:

job : i am sleeping 0
job : i am sleeping 1
job : i am sleeping 2
job : i am sleeping 3
job : i am sleeping 4
main: i am tired of waiting!
job: i am running finally
main: now i can quit

不能被取消的任务

在结构体中的try-finally中,再使用挂起操作,是不能被取消的。eg:

 runBlocking {
            val job = launch {

                try {
                    repeat(1000) { i ->
                        println("job: i am sleeping $i...")
                        delay(500L)
                    }
                } finally {
                    withContext(NonCancellable) {
                        println("job: i am running finally")
                        delay(5000)
                        println("job: and i have just delayed for 1 sec because i am non-cancellable")
                    }
                }
            }
            delay(1400)
            println("main: i am tired of waiting")
            job.cancelAndJoin()
            println("main: Now i can quit")
        }
2021-09-13 22:32:04.842 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 0...
2021-09-13 22:32:05.345 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 1...
2021-09-13 22:32:05.846 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 2...
2021-09-13 22:32:06.244 11979-11979/com.hujin.wan I/System.out: main: i am tired of waiting
2021-09-13 22:32:06.247 11979-11979/com.hujin.wan I/System.out: job: i am running finally
2021-09-13 22:32:11.249 11979-11979/com.hujin.wan I/System.out: job: and i have just delayed for 1 sec because i am non-cancellable
2021-09-13 22:32:11.249 11979-11979/com.hujin.wan I/System.out: main: Now i can quit

超时

可以使用withTimeout或者withTimeoutOrNull来对一个可能会超时的协程进行处理, withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常:

launch {
                withTimeout(1300L){
                    repeat(10000){i->
                        println("i am sleeping $i ...")
                        delay(500)
                    }
                }
            }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值