Kotlin学习笔记(九)协程简单概念与简单使用

先理清楚三种说法:

1、协程是轻量级线程、比线程耗费资源少
错。竟然还是官方说法。协程是语言层面的东西,而线程是操作系统层面的,就没啥可比性。 kotlin协程,和操作系统概念的协程不一样,理念有一点点像,但是没有任何关系。

2、协程是线程框架
对。就是对线程的封装,模糊了线程。

  • 协程中切换线程非常方便,由此解决了异步编程时过多的回调问题。
  • Kotlin 协程内部自己维护了线程池。
  • 在使用协程过程中,无需关注线程的切换细节,只需指定想要执行的线程类型即可。

这个问题可能不太直观。为便于理解协程的优势,贴一段代码。 代码1的执行线程,和代码4执行的线程,有可能是不一样的。从这个例子,你应该能理解协程的优势了吧? 这就是宣传的“用同步的方式,写异步的代码”的意思。

fun drive() {
    GlobalScope.launch(Dispatchers.IO) { //Dispatchers.IO,指定线程类型。后面是线程池。
        println("我坐车,我快乐")//1,开启协程1
        withContext(Dispatchers.Default) {//2, 开启协程2,并且切换到其他线程
            println("导游去订酒店")//3
        }
        println("车继续开,剩下团友在车上")//4, 再次执行协程1,线程切换到Dispatchers.IO线程池.
    }
}

3、协程效率高于线程
错。与第一点类似,协程与线程没有可比性。协程在运行方面的高效率,换成回调方式也是能够达成同样的效果。 协程内部本来就是回调实现的。只是在编译阶段封装了回调的细节而已。
 

在介绍如何使用前,先介绍一下,协程中几个重要的概念类。

  • CoroutineScope 

1. 协程作用域。它决定了线程调度池的范围和生命周期。 
2. 开发者可以在作用域里开协程干事情,相当于你创建了一个Task,然后submit给线程调度池。
3. 最后用cancel()方法关闭整个任务调度。类比于线程池的shutdown()。 
4. Android kotlin扩展库中,有两个比较好用的协程作用域,viewModelScope和lifecyceScope, 分别针对ViewModel和Activity/Fragment。当退出时,会自动帮你关闭协程作用域里的所有协程。

下面的示例,是开发者创建协程作用域时, 可以遵循的方式。

class ExampleClass {

    // 定义作用域,指定默认的协程执行线程。
    //Job()下节再讨论。
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // 用launch在作用域里创建一个协程任务
        scope.launch {
            // 协程任务执行的内容。
            fetchDocs()
        }
    }

    fun cleanUp() {
        // 取消作用域,并且会关闭作用域里所有的协程任务。
        scope.cancel()
    }
}
  • CoroutineContext

协程上下文,用来设置协程属性值,比如,控制协程的生命周期(Job&),指定在哪类线程中执行(Dispatchers),设置协程的名字(CoroutineName), 捕获协程抛出的异常(CoroutineExceptionHandler)等。

它们都有一个共同的特点,都是继承自CoroutineContext,是为了方便用一个链表串起来。CoroutineContext重载了Plus符号,下面的运算符重载函数目的,就是用+号做成一个链表。

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

 下面代码是设置CoroutineContext的范例:

    override fun init() {

        val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, Throwable ->

        }

        viewModelScope.launch(SupervisorJob() + Dispatchers.IO + CoroutineName("alan") + coroutineExceptionHandler) {

        }

    }
  • 协程常用函数:contextWith/runBlocking/launch/join/delay/async/await
  1. runBlocking,在当前线程执行,所以会阻塞当前线程,使得协程执行结束后才会执行runBlocking 后的代码。通常用于一些测试的场景。
  2. launch,协程启动函数,开启线程执行。launch()函数并不阻塞当前线程
  3. delay,挂起函数,但是不阻塞线程,因为任务会退到等待队列中去。它的过程如下:         1)构造task 加入到延迟队列里,此时协程挂起。2) 有个单独的线程会检测是否需要取出task并执行,没到时间的话就要挂起等待。 3)时间到了从延迟队列里取出并放入正常的队列,并从正常队列里取出执行。 4)task 执行的过程就是协程恢复的过程
  4. contextWith,切换线程。协程最核心的函数。
  5. async/await, launch的不足之处:协程执行没有返回值。然而,在有些场景我们需要返回值,此时轮到async/await 出场了。与launch 启动方式不同的是,async 的协程定义了返回值,是个泛型。并且async里使用的是DeferredCoroutine,顾名思义:延迟给结果的协程。
        fun testAsync() {
            runBlocking {
                //启动协程
                var job = GlobalScope.async {
                    println("job1 start")
                    Thread.sleep(10000)
                    //返回值
                    "fish"
                }
                //等待协程执行结束,并返回协程结果
                var result = job.await()
                println("result:$result")
            }
        }

  6. join,等待协程执行结束。虽然launch()函数不阻塞线程,但是我们就想要知道协程执行完毕没,进而根据结果确定是否继续往下执行,这时候该Job.join()出场了。

  • 协程异常处理

和线程一样,协程也会遇到,需要中止和异常中止,这两种。与线程处理方式类似:对于阻塞状态的协程,我们可以捕获异常,对于非阻塞的地方我们使用状态判断。

需要中止,

线程里就是:Thread.Interrupt()/Thread.Interrupted().

相应的协程里就是:job.isCancelled/job.cancel()

    //属性
     job.isActive //协程是否活跃
     job.isCancelled //协程是否被取消
     job.isCompleted//协程是否执行完成
     ...
    //函数
    job.join()//等待协程完成
    job.cancel()//取消协程
    job.invokeOnCompletion()//注册协程完成回调
    ...
    fun testCancel5() {
        runBlocking() {
            var job1 = launch(Dispatchers.IO) {
                try {
                    //挂起函数
                } catch (e : Exception) {
                    println("delay exception:$e")
                }
                if (!isActive) {
                    println("cancel")
                }
            }
        }
    }

异常中止:

也就是对抛异常的处理。分为在子协程里捕获和全局捕获。

子协程里捕获:try/catch不要加到协程外面,因为主线程不能捕获子线程的异常。

    fun testException2() {
        runBlocking {
            var job1 = launch(Dispatchers.IO) {
                try {
                    println("job1 start")
                    //异常
                    1 / 0
                    println("job1 end")
                } catch (e : Exception) {
                    println("e=$e")
                }
            }
        }
    }

全局捕获:

与线程类似,协程也可以全局捕获异常。就是设置我们前面讲到的CoroutineExceptionHandler。虽然能够捕获异常,但是发生异常的协程还是不能往下执行了。

    //创建处理异常对象
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("handle exception:$exception")
    }
    fun testException3() {
        runBlocking {
            //声明协程作用域
            var scope = CoroutineScope(Job() + exceptionHandler)
            var job1 = scope.launch(Dispatchers.IO) {
                println("job1 start")
                //异常
                1 / 0
                println("job1 end")
            }
        }
    }

SupervisorJob:

关于异常不得不讲一下SupervisorJob。

当默认或者设置Job()的时候。子协程发生异常后,会取消父协程、同级兄弟协程的执行,这在有些场景是不合理的,因为伤害范围太广,明明是一个子协程的锅,非得所有协程来背。

SupervisorJob它的作用,就是谁的锅谁背。

好了,协程的简单讲解就到此了。虽然是简单,但也触及了核心,是理解协程的基础。

引用:

用了20多张图终于把协程上下文CoroutineContext彻底搞懂了-阿里云开发者社区

协程系列文章——同一作者,讲原理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值