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


    先理清楚三种说法:

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

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

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

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

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

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

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

    • CoroutineScope 

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

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

    1. class ExampleClass {
    2. // 定义作用域,指定默认的协程执行线程。
    3. //Job()下节再讨论。
    4. val scope = CoroutineScope(Job() + Dispatchers.Main)
    5. fun exampleMethod() {
    6. // 用launch在作用域里创建一个协程任务
    7. scope.launch {
    8. // 协程任务执行的内容。
    9. fetchDocs()
    10. }
    11. }
    12. fun cleanUp() {
    13. // 取消作用域,并且会关闭作用域里所有的协程任务。
    14. scope.cancel()
    15. }
    16. }
    • CoroutineContext

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

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

    1. public operator fun plus(context: CoroutineContext): CoroutineContext =
    2. if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
    3. context.fold(this) { acc, element ->
    4. val removed = acc.minusKey(element.key)
    5. if (removed === EmptyCoroutineContext) element else {
    6. // make sure interceptor is always last in the context (and thus is fast to get when present)
    7. val interceptor = removed[ContinuationInterceptor]
    8. if (interceptor == null) CombinedContext(removed, element) else {
    9. val left = removed.minusKey(ContinuationInterceptor)
    10. if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
    11. CombinedContext(CombinedContext(left, element), interceptor)
    12. }
    13. }
    14. }

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

    1. override fun init() {
    2. val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, Throwable ->
    3. }
    4. viewModelScope.launch(SupervisorJob() + Dispatchers.IO + CoroutineName("alan") + coroutineExceptionHandler) {
    5. }
    6. }
    • 协程常用函数: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,顾名思义:延迟给结果的协程。
      1. fun testAsync() {
      2. runBlocking {
      3. //启动协程
      4. var job = GlobalScope.async {
      5. println("job1 start")
      6. Thread.sleep(10000)
      7. //返回值
      8. "fish"
      9. }
      10. //等待协程执行结束,并返回协程结果
      11. var result = job.await()
      12. println("result:$result")
      13. }
      14. }

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

    • 协程异常处理

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

    需要中止,

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

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

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

    异常中止:

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

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

    1. fun testException2() {
    2. runBlocking {
    3. var job1 = launch(Dispatchers.IO) {
    4. try {
    5. println("job1 start")
    6. //异常
    7. 1 / 0
    8. println("job1 end")
    9. } catch (e : Exception) {
    10. println("e=$e")
    11. }
    12. }
    13. }
    14. }

    全局捕获:

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

    1. //创建处理异常对象
    2. val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    3. println("handle exception:$exception")
    4. }
    5. fun testException3() {
    6. runBlocking {
    7. //声明协程作用域
    8. var scope = CoroutineScope(Job() + exceptionHandler)
    9. var job1 = scope.launch(Dispatchers.IO) {
    10. println("job1 start")
    11. //异常
    12. 1 / 0
    13. println("job1 end")
    14. }
    15. }
    16. }

    SupervisorJob:

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

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

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

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

    引用:

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

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

  • 相关阅读:
    MongoDB
    c++基础(六)——深拷贝与浅拷贝
    echarts 绘制仿立体 矩形 渐变柱状图图表
    TodoMVC 与 Thymeleaf 和 HTMX
    AWS Academy LMS 考勤 - 教师
    LQ0141 纸张尺寸【水题】
    Linux基础系列(三)——压缩命令、网络命令、关机重启
    【PX4学习笔记】06.PID控制原理
    Map集合保存数据库
    AI工程化—— 如何让AI在企业多快好省的落地?
  • 原文地址:https://blog.csdn.net/zjuter/article/details/120269227