7.4 KiB
Kotlin协程从入门到入土
0x00 什么是协程
协程,英文Coroutines,是一种比线程更加轻量级的存在。
协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。
协程已经在Python、JS等许多编程语言上有所实现,一般情况下,协程被认为是比线程更轻量级的异步事件处理方式,所谓协程,是在编程语言层面上对同时执行多个异步任务上的实现。
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。
协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码
简单来说,协程可以帮助开发者以同步方式编写异步代码,解决以往编写异步程序时出现的“回调地狱”的问题。
0x11 Kotlin上协程的分类
创建最简单的协程
先上代码:
// 调用阻塞式协程
runBlocking {
Log.d("runBlocking", "启动一个协程")
}
// 调用launch启动协程
GlobalScope.launch {
Log.d("launch", "启动一个协程")
}
// 调用async启动协程
GlobalScope.async {
Log.d("async", "启动一个协程")
}
如上面代码所示,Kotlin中创建启动协程主要有3种方式:
- runBlocking - 启动一个阻塞式协程,协程体代码块内代码会阻塞所在线程,返回值为泛型T,就是在协程体内最后一行返回的数据类型
- launch - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为Job
- async - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为一个Deferred,T泛型为协程体内最后一行返回的数据类型。
- 协程的返回类型
-
runBlocking的泛型返回值T runBlocking在默认情况下类似于一个封装好的方法,当然可以通过指定该方法运行的线程,让这个方法不阻塞主线程,这样就类似于我们在Java中的Thread,并且将run方法执行的结果最终返回回来,这里不做过多讨论。
-
launch的返回类型Job
Job我们可以认为他就是一个协程作业是通过CoroutineScope.launch生成的,同时它运行一个指定的代码块,并在该代码块完成时完成。我们可以通过isActive、isCompleted、isCancelled来获取到Job的当前状态
我们可以通过Job获取当前协程执行的状态,如下表所示,协程也存在对应的生命周期。
State [isActive] [isCompleted] [isCancelled] New (optional initial state) false false false Active (default initial state) true false false Completing (transient state) true false false Cancelling (transient state) false false true Cancelled (final state) false true true Completed (final state) false true false -
async的返回类型Deferred
Deferred是继承自Job的,我们可以通过Deferred.await()方法获取协程的泛型T返回值。
0x12 详解协程参数
上面的三种创建启动协程的方法,其构造方法内的参数都很类似。
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
):
可以看到,runBlocking相比其他两个方法,缺少了协程启动模式CoroutineStart类型的参数,三个方法参数都有协程下上文CoroutineContext和协程执行体CoroutineScope.()。接下来我们就主要了解这三个参数类型。
-
协程上下文CoroutineContext
它是一个包含了用户定义的一些各种不同元素的Element对象集合。其中主要元素是Job、协程调度器CoroutineDispatcher、还有包含协程异常CoroutineExceptionHandler、拦截器ContinuationInterceptor、协程名CoroutineName等。这些数据都是和协程密切相关的,每一个Element都一个唯一key。通过协程上下文,我们可以定义协程调度器、协程名称、协程的异常处理方式等等,而这些都可以通过协程上下文中的重载关键字来实现。
-
协程调度器CoroutineDispatcher 如上文所述,协程调度器也实现了协程上下文的接口,协程调度器可以指定当前协程所在的线程。
Kotlin默认提供了4中类型的协程调度线程:
- Default:默认调度器,CPU密集型任务调度器,适合处理后台计算。通常处理一些单纯的计算任务,或者执行时间较短任务。比如:Json的解析,数据计算等。
- IO:IO调度器,,IO密集型任务调度器,适合执行IO相关操作。比如:网络处理,数据库操作,文件操作等。
- Main:UI调度器, 即在主线程上执行,通常用于UI交互,刷新等。
- Unconfined:非受限调度器,又或者称为“无所谓”调度器,不要求协程执行在特定线程上。
不指定调度器时,协程运行在Default默认调度器下,需要注意的是,Default调度器对应的线程并非为主线程。
-
协程启动模式
CoroutineStart协程启动模式,是启动协程时需要传入的第二个参数。协程启动有4种- DEFAULT 默认启动模式,我们可以称之为饿汉启动模式,因为协程创建后立即开始调度,虽然是立即调度,单不是立即执行,有可能在执行前被取消。
- LAZY 懒汉启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度。也就是说只有我们主动的调用Job的start、join或者await等函数时才会开始调度。
- ATOMIC 一样也是在协程创建后立即开始调度,但是它和DEFAULT模式有一点不一样,通过ATOMIC模式启动的协程执行到第一个挂起点之前是不响应cancel 取消操作的,ATOMIC一定要涉及到协程挂起后cancel 取消操作的时候才有意义。
- UNDISPATCHED 协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。这听起来有点像 ATOMIC,不同之处在于UNDISPATCHED是不经过任何调度器就开始执行的。当然遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。
这里需要特别注意的是UNDISPATCHED,非调度启动模式,在这个模式下,协程会在父协程所在线程直接执行,直到挂起函数出现,如果指定了对应的调度器,则会切换到对应的线程执行。