深色模式
协程基础概念
我对协程的理解
学习协程的目标:
- 弄清楚协程相关知识点之间的联系。
- 对协程的用法归纳总结。
协程的使用
launch, async, runBlocking
GlobalScope, MainScope, lifecycleScope, viewModelScope
Dispatchers
CoroutineStart
withTimeout
Java 调用 Kotlin 挂起函数
PayDialog.newInstance().apply { onWechatPay = { payHolder.wechatPay1(source.productId, source.orderTypeId) } onAlipay = { payHolder.alipay1(source.productId, source.orderTypeId) } }.show(supportFragmentManager, "PayDialog") }
协程的原理
- 结构化并发
- 异常和取消
几个概念:
- 协程作用域,创建协程作用域的方式
- 挂起函数,其实就是callback
- 协程上下文
- 协程启动模式
- 协程调度器
它们之间的关系:
- 协程作用域
- 协程上下文
- Job
- 协程调度器
- 协程上下文
协程的特点
- 轻量:创建协程的开销很小,协程挂起不会阻塞线程,挂起比阻塞开销小。
- 内存泄漏少:结构化并发。
- 可取消:协程支持取消。
- JetPack集成:许多JetPack库都提供协程扩展,方便安卓开发。
协程异常处理机制
协程异常的分类
协程处理异常的时候,会把异常分为两类:
- 取消异常:由
cancel()
导致的异常,超时也属于取消的一种。 - 一般异常:取消异常之外的异常。
看看这些异常类的继承关系:
Exception
(一般异常)RuntimeException
(一般异常)IllegalStateException
(一般异常)CancellationException
(取消异常)TimeoutCancellationException
(取消异常)
IndexOutOfBoundsException
(一般异常)
...Exception
(一般异常)
很简单,只有CancellationException
及其子类是取消异常,其它都是一般异常。
之所以要这么分类,是因为,简单来说:
- 取消异常,向下传递。
- 一般异常,双向传递。
协程的异常处理机制,是协程结构化并发的基础。
协程的取消
协程的取消是协作的。
一般情况下,协程遵循结构化并发,如果父协程被取消,那么它的所有子协程也会被取消。
协程的异常(一般异常)
协程构建器处理异常的方式
按照协程中异常的传播方式,协程构建器可以分为两种:
- 自动传播异常,launch,actor
- 向用户暴露异常,async,produce
当这些构建器用于创建根协程时,异常的传播行为有区别。
对于launch类构建器:
CoroutineExceptionHandler
用于处理未捕获的异常,只有根协程的handler会起作用,因为子协程的handler会把异常委托给父协程
对于async类构建器:
它会捕获异常,并将其表示在Deffered对象中。所以它的handler没有作用。
异常传递的例外:监督作用域
协程构建器
有哪些协程构建器?
以下5个函数,都定义在kotlinx.coroutines.Builders.kt
中
CoroutineScope.launch()
Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job. The coroutine is cancelled when the resulting job is cancelled.
启动一个新的协程,不阻塞当前线程,并且返回这个协程的引用,这个引用用一个Job
对象表示。当这个Job
调用cancel()
时,此协程会取消。
仔细看此函数的签名,它是一个扩展函数,有3个参数:
context
start
block
kotlin
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
CoroutineScope.async()
Creates a coroutine and returns its future result as an implementation of Deferred. The running coroutine is cancelled when the resulting deferred is cancelled. The resulting coroutine has a key difference compared with similar primitives in other languages and frameworks: it cancels the parent job (or outer scope) on failure to enforce structured concurrency paradigm. To change that behaviour, supervising parent (SupervisorJob or supervisorScope) can be used.
kotlin
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
runBlocking()
Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
运行一个新的协程,阻塞当前线程,直到协程完成。这个函数的作用是,在测试时,用于main()
函数中。
kotlin
fun main() = runBlocking {
}
withContext()
kotlin
val result = withContext(Dispatchers.Default){
delay(1000)
1 + 1
}
// result is 2
相当于
kotlin
val result = async {
delay(1000)
1 + 1
}.await()
CoroutineDispatcher.invoke()
是withContext()
的另一种写法
kotlin
Dispatchers.Default {
}
相当于
kotlin
withContext(Dispatchers.Default){
}
协程作用域 CoroutineScope
协作式取消,父协程会等待子协程completed,才会completed。
CoroutineScope
有launch
和async
方法,用来创建协程。
各种不同CoroutineScope的区别:
GlobalScope
:全局范围,不会自动结束执行。MainScope()
:主线程的作用域,全局范围。lifecycleScope
:生命周期范围,用于Activity等有生命周期的组件,在DESTROYED的时候会自动结束。viewModelScope
:viewModel范围,用于ViewModel中,在ViewModel被回收时会自动结束。
coroutineScope
supervisorScope
协程启动模式 CoroutineStart
DEFAULT
:默认启动模式,我们可以称之为饿汉启动模式,因为协程创建后立即开始调度,虽然是立即调度,单不是立即执行,有可能在执行前被取消。LAZY
:懒汉启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度。也就是说只有我们主动的调用Job
的start()
、join()
或者await()
等函数时才会开始调度。ATOMIC
:一样也是在协程创建后立即开始调度,但是它和DEFAULT
模式有一点不一样,通过ATOMIC
模式启动的协程执行到第一个挂起点之前是不响应cancel
取消操作的,ATOMIC
一定要涉及到协程挂起后cancel
取消操作的时候才有意义。UNDISPATCHED
:协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。这听起来有点像ATOMIC
,不同之处在于UNDISPATCHED
是不经过任何调度器就开始执行的。当然遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。
协程句柄 Job
Job
是协程的句柄。使用 launch
或 async
创建的每个协程都会返回一个 Job
实例,该实例是相应协程的唯一标识并管理其生命周期。
Job
可以:
查询协程的状态:
isActive
:isCompleted
isCancelled
生命周期相关操作:
cancel()
:用于Job的取消,取消协程start()
:用于启动一个协程,在该协程是CoroutineStart.LAZY
的情况下invokeOnCompletion()
:添加一个监听,当工作完成或者异常时会调用join()
:阻塞并等候当前协程完成
协程上下文 CoroutineContext
CoroutineContext
是一个包含了用户定义的一些各种不同元素的Element
对象集合,CoroutineContext
使用以下元素集定义协程的行为:
Job
:控制协程的生命周期。CoroutineDispatcher
:将工作分派到适当的线程。ContinuationInterceptor
:拦截器CoroutineName
:协程的名称,可用于调试。CoroutineExceptionHandler
:处理未捕获的异常。
协程调度器 CoroutineDispatcher
调度器它确定了相关的协程在哪个线程或哪些线程上执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。
Dispatchers.Default
:默认调度器,CPU密集型任务调度器,适合处理后台计算。通常处理一些单纯的计算任务,或者执行时间较短任务。比如:Json的解析,数据计算等Dispatchers.IO
:IO调度器,,IO密集型任务调度器,适合执行IO相关操作。比如:网络处理,数据库操作,文件操作等Dispatchers.Main
:UI调度器, 即在主线程上执行,通常用于UI交互,刷新等Dispatchers.Unconfined
:非受限调度器,又或者称为“无所谓”调度器,不要求协程执行在特定线程上。ExecutorCoroutineDispatcher
:线程池调度器。有多种方式可以创建线程池调度器:
ExecutorService.asCoroutineDispatcher()
:将线程池转换为线程池调度器。Executor.asCoroutineDispatcher()
:将线程池转换为线程池调度器。newSingleThreadContext()
:单线程的线程池调度器。newFixedThreadPoolContext()
:指定线程数的线程池调度器。