一、协程的挂起与恢复
suspend——也称为挂起或者暂停,用于暂定当前执行当前协程,并保存所有局部变量
resume——用于让暂定的协程从其暂定处继续执行
1.挂起函数
- 使用suspend关键字修饰的函数叫挂起函数
- 挂起函数只能在协程体内或者其他挂起函数内调用
二、协程的两部分
- 基础设施层 标准库的协程API,主要对协程提供了概念和语义上最基本的支持
- 业务架构层 协程上层架构支持
三、协程的调度器
所有的协程必须在调度器中运行,即使他们在主线程上运行也是如此。
- Dispatchers.Main Android 上主线程用他来处理UI交互和一些轻量级任务。
a.调用suspend函数
b.调用UI函数
c.更新LiveData - Dispatchers.IO 非主线程 专为磁盘和网络IO进行了优化。
a.数据库
b.文件读写
c.网络处理 - Dispatchers.Default 非主线程 专门为CPU密集型任务进行了优化,在没有指定调度器的情况下,默认使用Dispatchers.Default
a.数组排序
b.JSON数据解析
c.处理差异判断
四、协程的任务泄露
- 当某个协程任务丢失,无法追踪,会导致内存、CPU、磁盘等资源的浪费,甚至发送一个无用的网络请求,这种情况称之为任务泄露。
- 为了能避免协程泄露,kotlin 引入了结构化并发机制。
结构化并发可以做到以下几点
a.取消任务,某项任务不需要时取消他。
b.追踪任务,当任务正在执行时,追踪他
c.发出错信号,当协程失败时,发出错误信号表明有错误发生。
定义协程必指定其CoroutineScope ,它会跟踪所有协程,同样他还会取消所有由它启动的协程。
常用的API:
a.GlobalScope 生命周期是process级别,即使Activity或Fragment已经被销毁,协程任然在执行。
b.MainScope 在Activity中使用,可以在onDestory()中取消。
c.viewModelScope 只能在ViewModel中使用,绑定ViewModel的生命周期。
d.lifecycleScope 只能Activity、Fragment中使用,会绑定Activity和Fragment的生命周期。
五、协程构建器
1.launch月async构建器都用来启动新协程。
- launch 返回一个job并且不附带任何结果值。
- async 返回一个Deferred,Deferred也是一个job,可以使用.await()在一个延期的值上得到最终结果。
2.等待一个作业
- join()和await()
val duration = measureTimeMillis {
val job1 = launch {
delay(200)
println("one finished")
}
job1.join()
val job2 = launch {
delay(200)
println("two finished")
}
job2.join()
val job3 = launch {
delay(200)
println("three finished")
}
job3.join()
}
println("join duration :${duration}")
val duration = measureTimeMillis {
val job1 = async {
delay(200)
println("one finished")
1
}
val job2 = async {
delay(200)
println("two finished")
2
}
val job3 = async {
delay(200)
println("three finished")
3
}
println("${job1.await()+job2.await()+job3.await()}")
}
println("duration :${duration}")
六、协程的启动模式
- DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态。
- ATOMOC:协程创建后立即进行调度,协程执行到第一个挂起点之前不响应取消。
- LAZY:只有协程被需要时,包括主动调用协程start、join或await时候协程才开始调度,如果调取点调用取消,那么协程将会直接进入异常结束状态。
- UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点。
六、协程的作用域构建器
1.coroutineScope和runBlocking
- runBlocking是常规函数,而coroutineScope是挂起函数
- 他们都会等待其协程体以及子协程结束,主要区别在于runBlocking方法会阻塞当前线程来等待,coroutineScope只是挂起,会释放底层线程用于其他用途。
2.corouttineScope与supervisorScope - coroutineScope:一个协程失败了,所有其他兄弟协程也会被取消。
- supervisorScope:一个协程失败了,不会影响其他兄弟协程。
七、Job对象
- 对于每个创建的协程(launch或async),会返回一个job实例,该实例是协程的唯一标示,并负责管理协程的生命周期。
- 一个任务可以包含一系列状态:新创建(New),活跃(Active),完成中(Completed),取消(Cancelling)和已取消(Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问Job的属性:isActive、isCancelled、isCompleted。
1、协程的取消
- 取消协程会取消它的子协程
- 被取消的子协程并不影响其余兄弟协程
- 协程通过抛出一个特殊异常 CancellationException来处理取消操作。
- 所有kotlinx.coroutines中的挂起函数(withContext、delay等)都是可取消的。
这里注意一点,CPU密集型的task不能被取消,任务调度器为Dispatchers.Default(可通过isAcitve或者ensureActive()来判断),此外,还可以使用yield函数检查CPU密集型task所在的协程状态,如果已经取消,则抛出CancellationException予以响应,并尝试让出线程的执行权,给其他协程提供执行机会。
2.协程取消的副作用
- 在finally中释放资源
- use 函数 :该函数只能被实现了Closeable的对象使用,程序结束的时候会调用close方法,适合文件对象。
3.不能取消的任务
- 处于取消状态的协程不能够挂起,当协程被取消后需要调用挂起函数,我们需要将清理任务的代码放置在NonCancellable CoroutineContext中。
这样会挂起运行的代码,并保持协程的取消中状态直到任务处理完成
4.超时任务
- 很多情况下取消一个行程的理由是它可能超时。
- withTimeoutOrNull 通过返回null来进行超时操作,从而替代抛出一个异常。
val result = withTimeoutOrNull(5000L) {
repeat(1000){
println("im repeating")
}
"Done"
}
八、协程的上下文
1.组合上下文中的元素
有时候我们需要在协程上下文中定义多个元素。我们可以使用+操作符来实现。比如我们可以显示指定一个调度器来启动协程并且同时显示指定一个命名:
fun testCoroutineContext() = runBlocking<Unit> {
launch(Dispatchers.Default + CoroutineName("test")) {
println("Working in Thread ${Thread.currentThread().name}")
}
}
2.协程上下文的继承
a. 对于新的协程,它的CoroutineContext 会包含一个全新的Job实例,它会帮助我们控制协程的生命周期。而剩下的元素从CoroutineContext的父类继承,该父类可能是另外一个协程或者创建该协程的CoroutineScope。
fun testExtendCoroutineContext() = runBlocking<Unit> {
val scope = CoroutineScope(Job() + Dispatchers.Default + CoroutineName("test"))
val job = scope.launch {
println("${coroutineContext[Job]}Working in Thread ${Thread.currentThread().name}")
val result = async {
println("${coroutineContext[Job]}Working in Thread ${Thread.currentThread().name}")
"OK"
}.await()
}
job.join()
}
b. 协程的上下文 = 默认值+继承的CoroutineContext+参数
- 一些元素包含默认值:Dispatchers.Default是默认的CoroutineDispatcher,以及"coroutine"作为默认的CoroutineName;
- 继承的CoroutineContext是CoroutineScope或者其父协程的CoroutineContext
- 传入协程构建器的参数优先级高于继承的上下文参数,所以会覆盖对应的参数值。
八、协程的异常处理
1、异常的传播:
协程构建器有两种形式:
- 自动传播异常(launch与actor),向用户暴露异常(async与produce)当这些构建器用于出创建协程时(该协程不是另一个协程的子协程),前者这类构建器,异常会在它发生异常第一时间被抛出,而后者则依赖用户来最终取消消费异常,例如通过await或者receive。
fun testHandleException() = runBlocking<Unit> {
val job = GlobalScope.launch {
try {
throw IndexOutOfBoundsException()
} catch (e: Exception) {
println("catched IndexOutOfBoundsException")
}
}
job.join()
//async exception
val deferred = GlobalScope.async {
throw ArithmeticException()
}
try {
//消费
deferred.await()
} catch (e: Exception) {
println("cached ArithmeticException")
}
}
- 非根协程的异常
其他协程所创建的协程中,产生的异常总会被传播。
//直接抛出异常
fun testHandleException2() = runBlocking<Unit> {
val scope = CoroutineScope(Dispatchers.Default+CoroutineName("test"))
val job = scope.launch {
async {
throw IndexOutOfBoundsException()
}
}
job.join()
}
2、异常传播的特性
当一个协程由于异常而运行失败时,它会传播这个异常并传递给它的父级。接下来父级会进行下面几个操作:
- 取消它自己的子级
- 取消它自己
- 将异常传播并传递给它的父级
3、SupervisorJob
使用SupervisorJob时,一个子协程的运行失败不会影响其他子协程。SupervisorJob不会传播异常给它的父协程。
fun testSupervisorJob() = runBlocking<Unit> {
val supervisorScope =
CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("test"))
val job1 = supervisorScope.launch {
delay(1000)
println("child 1")
throw IndexOutOfBoundsException()
}
val job2 = supervisorScope.launch {
try {
delay(3000)
} finally {
println("child2 finish")
}
}
joinAll(job1, job2)
}
//最终结果是打印job1发生异常结束,但是job2正常执行
4、异常的捕获
- 使用CoroutineExceptionHandler对协程的异常进行捕获
- 以下的条件满足时,异常就会被捕获
a.时机:异常是被自动抛出异常的协程所抛出(使用launch,而不是async时)
b.位置:CoroutineExceptionHandler在CoroutineScope的CoroutineContext中或在一个根协程(CoroutineScope或者supervisorScope的直接子协程)中。
5、异常的聚合
fun testExceptionSuppressed() = runBlocking<Unit> {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception ${exception.suppressed.contentToString()}")
}
val job = GlobalScope.launch(exceptionHandler) {
launch {
try {
delay(Long.MAX_VALUE)
}finally {
throw ArithmeticException()
}
}
launch {
delay(100)
throw IOException()
}
}
job.join()
}
//打印结果
//Caught java.io.IOException [java.lang.ArithmeticException]