110 lines
7.4 KiB
Markdown
110 lines
7.4 KiB
Markdown
# Kotlin协程从入门到入土
|
||
|
||
## 0x00 什么是协程
|
||
协程,英文Coroutines,是一种比线程更加轻量级的存在。
|
||
|
||
协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。
|
||
|
||
协程已经在Python、JS等许多编程语言上有所实现,一般情况下,协程被认为是比线程更轻量级的异步事件处理方式,所谓协程,是在编程语言层面上对同时执行多个异步任务上的实现。
|
||
|
||
> 协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。
|
||
>
|
||
> 协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码
|
||
|
||
简单来说,协程可以帮助开发者以同步方式编写异步代码,解决以往编写异步程序时出现的“回调地狱”的问题。
|
||
|
||
## 0x11 Kotlin上协程的分类
|
||
|
||
### 创建最简单的协程
|
||
|
||
先上代码:
|
||
```Kotlin
|
||
// 调用阻塞式协程
|
||
runBlocking {
|
||
Log.d("runBlocking", "启动一个协程")
|
||
}
|
||
|
||
// 调用launch启动协程
|
||
GlobalScope.launch {
|
||
Log.d("launch", "启动一个协程")
|
||
}
|
||
|
||
// 调用async启动协程
|
||
GlobalScope.async {
|
||
Log.d("async", "启动一个协程")
|
||
}
|
||
```
|
||
如上面代码所示,Kotlin中创建启动协程主要有3种方式:
|
||
1. runBlocking - 启动一个阻塞式协程,协程体代码块内代码会阻塞所在线程,返回值为泛型T,就是在协程体内最后一行返回的数据类型
|
||
2. launch - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为Job
|
||
3. async - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为一个Deferred<T>,T泛型为协程体内最后一行返回的数据类型。
|
||
|
||
- 协程的返回类型
|
||
1. runBlocking的泛型返回值T
|
||
runBlocking在默认情况下类似于一个封装好的方法,当然可以通过指定该方法运行的线程,让这个方法不阻塞主线程,这样就类似于我们在Java中的Thread,并且将run方法执行的结果最终返回回来,这里不做过多讨论。
|
||
|
||
2. 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 |
|
||
|
||
3. async的返回类型Deferred<T>
|
||
Deferred是继承自Job的,我们可以通过Deferred.await()方法获取协程的泛型T返回值。
|
||
|
||
## 0x12 详解协程参数
|
||
上面的三种创建启动协程的方法,其构造方法内的参数都很类似。
|
||
```Kotlin
|
||
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,非调度启动模式,在这个模式下,协程会在父协程所在线程直接执行,直到挂起函数出现,如果指定了对应的调度器,则会切换到对应的线程执行。
|
||
-
|
||
|
||
|
||
|