type
status
date
slug
summary
tags
category
icon
password

概念

协程运行在协程上下文中(CoroutineContext)。
协程上下文包含一个协程调度器(CoroutineDispatcher),它可以将协程限制在一个特定的线程中执行,或者将协程分配到一个线程池中,或者让它不受限制的运行。
CoroutineContext使用以下元素(Element)定义协程的行为:
  • Job: 控制协程的生命周期
  • CoroutineDispatcher: 将工作(协程)分派到适当的线程
  • CoroutineName: 协程的名字,可用于调试
  • CoroutineExceptionHandler: 处理未捕获的异常
CoroutineScope会跟踪它使用launch或者async创建的所有协程,可以随时调用scope.cancel()取消正在运行的协程.但是,已取消的scope是不能再创建协程的,这一点需要注意.
与调度程序不同,CoroutineScope并不运行协程.
Job是协程的句柄,使用launch或者async创建的每个协程都会返回一个job对象,这个对象唯一标识协程并管理协程的生命周期,当然也可以将Job传递给CoroutineScope进一步管理其生命周期.执行job.cancel()不影响CoroutineScope.
协程可以在一个线程上挂起,并在其他线程上恢复.

Dispatcher

主要使用3种Dispatcher:
  • Dispatchers.Main
    • 运行在主线程
  • Dispatchers.Default
    • 适合主线程之外占有大量CPU资源的工作
可以使用withContext(Dispatcher)来指定:
withContext在性能上有优势,可以避免频繁的线程切换.

启动协程

两种方式:
  • launch
    • 不返回结果,从常规函数启动协程一般用launch,因为常规函数无法调用await
  • async
    • 返回结果 (使用await),在另一个协程中或者在挂起函数中且在执行并行分解时才使用async
两者处理异常的方式不同,async持有异常,并作为结果在await中返回.因此,如果使用await从常规函数启动,则会丢弃异常信息.
在普通方法中启动协程一般使用下面两种方法:
  • launch() (不会阻塞当前线程)
  • runBlocking {} (一般用于测试,将后台认为进行同步处理,防止过早退出)

并行分解

由suspend函数启动的所有协程都必须在函数返回结果之前停止,因此需要保证这些协程在返回结果之前完成.

自定义CoroutineScope

GlobalScope

使用GlobalScope.launch 的时候,会创建一个顶层协程,如果忘记保持对新启动的协程的引用,它还会继续运行,如果挂起了(比如delay了太久),必须手动保持对所有已经启动协程的引用.并调用join()方法.
在GlobalScope中启动的活动协程并不会使进程保活,它们就像守护线程.

阻塞与非阻塞

先看一段代码:
你觉得会输出什么结果?
结果是:
对,GlobalScope.launch中的代码快并没有执行到打印“World”.
这个时候可以再主协程中添加一个delay,但是方法未免太过死板,可以使用JOb控制:
但是,如果有很多协程,每个都去获取job并join的话也太容易出错了,因此:
直接在对应的coroutineScope中启动协程,而不是使用GlobalScope.

调用顺序问题

  • 顺序调用
    • async并发
      • Lazy 的 async并发
        • 只有结果通过await获取的时候协程才会启动,或者在Job的start函数调用的时候.

      参考文档

      1. https://developer.android.com/kotlin/coroutines-adv?hl=zh-cn
       
      Kotlin中的CoroutineScopeLayoutInflater源码分析
      Loading...