@TryLoveCatch
2024-03-23T10:18:20.000000Z
字数 17800
阅读 490
Kotlin知识体系
协程是一个基于线程的上层框架!
它和Executor、AsyncTask一样,都是对 Thread API 的封装,让我们可以在写代码时,不用关注多线程就能够很方便地写出并发操作。
挂起,就是一个稍后会被自动切回来的线程调度操作。
挂起的对象是协程。
挂起的全名应该叫非阻塞式挂起,其意思是为不会阻塞其他协程,只是当前自己所在协程会挂起等待不执行,但是其他协程还是能继续执行的。
挂起是针对其他协程的,如果只有一个协程,那就是和阻塞的效果一样了
挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作。
顶层函数,创建一个新的协程同时阻塞当前线程,直到其内部所有逻辑以及子协程所有逻辑全部执行完成,返回值是泛型T,一般在项目中不会使用,主要是为main函数和测试设计的。
定义如下:
fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
看一个例子:
fun runBloTest() {
"1111: ${Thread.currentThread().name}".loge("TryLoveCatch")
//创建一个全局作用域协程,不会阻塞当前线程,生命周期与应用程序一致
runBlocking {
"2222: ${Thread.currentThread().name}".loge("TryLoveCatch")
//在这1000毫秒内该协程所处的线程不会阻塞
//协程将线程的执行权交出去,该线程继续干它要干的事情,到时间后会恢复至此继续向下执行
delay(1000)//1秒无阻塞延迟(默认单位为毫秒)
"3333: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
"4444: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
结果如下:
2023-06-03 16:45:44.479 21446-21446/TryLoveCatch: 1111: main
2023-06-03 16:45:44.482 21446-21446/TryLoveCatch: 2222: main
// 1S后
2023-06-03 16:45:45.485 21446-21446/TryLoveCatch: 3333: main
2023-06-03 16:45:45.487 21446-21446/TryLoveCatch: 4444: main
只有在runBlocking协程体逻辑全部运行结束后,声明在runBlocking之后的代码才能执行,即runBlocking会阻塞其所在线程。
runBlocking
本身是在当前线程,不会新开始一个线程。虽然会阻塞当前线程的,但其内部运行的协程又是非阻塞的。
launch是最常用的用于启动协程的方式,用于在不阻塞当前线程的情况下启动一个协程,并返回对该协程任务的引用,即Job对象。
定义如下:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
在非协程环境中launch有两种方式创建协程:
在应用范围内启动一个新协程,不会阻塞调用线程,协程的生命周期与应用程序一致。
表示一个不绑定任何Job的全局作用域,用于启动顶层协程,这些协程在整个应用程序生命周期中运行,不会提前取消(不存在Job)。
看一个例子:
fun launchTest() {
"1111: ${Thread.currentThread().name}".loge("TryLoveCatch")
//创建一个全局作用域协程,不会阻塞当前线程,生命周期与应用程序一致
GlobalScope.launch {
"2222: ${Thread.currentThread().name}".loge("TryLoveCatch")
//在这1000毫秒内该协程所处的线程不会阻塞
//协程将线程的执行权交出去,该线程继续干它要干的事情,到时间后会恢复至此继续向下执行
delay(1000)//1秒无阻塞延迟(默认单位为毫秒)
"3333: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
"4444: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
结果如下:
2023-06-03 16:41:59.808 19810-19810/TryLoveCatch: 1111: main
2023-06-03 16:41:59.814 19810-19810/TryLoveCatch: 4444: main
2023-06-03 16:41:59.815 19810-19857/TryLoveCatch: 2222: DefaultDispatcher-worker-1
// 1S后
2023-06-03 16:42:00.821 19810-19857/TryLoveCatch: 3333: DefaultDispatcher-worker-1
GlobalScope.launch
会新起一个线程,可以看出来是一个线程池。
启动一个新的协程而不阻塞当前线程,并返回对协程的引用作为一个Job。
通过CoroutineContext至少一个协程上下文参数创建一个 CoroutineScope对象。
协程上下文控制协程生命周期和线程调度,使得协程和该组件生命周期绑定,组件销毁时,协程一并销毁,从而实现安全可靠地协程调用。
这是在应用中最推荐使用的协程使用方式。
看一个例子:
fun launchTest() {
"1111: ${Thread.currentThread().name}".loge("TryLoveCatch")
//开启一个IO模式的协程,通过协程上下文创建一个CoroutineScope对象,需要一个类型为CoroutineContext的参数
val job = CoroutineScope(Dispatchers.IO).launch {
"2222: ${Thread.currentThread().name}".loge("TryLoveCatch")
delay(1000)//1秒无阻塞延迟(默认单位为毫秒)
"3333: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
"4444: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
结果如下:
2023-06-03 16:52:21.651 23565-23565/TryLoveCatch: 1111: main
2023-06-03 16:52:21.664 23565-23565/TryLoveCatch: 4444: main
2023-06-03 16:52:21.666 23565-23634/TryLoveCatch: 2222: DefaultDispatcher-worker-1
2023-06-03 16:52:22.670 23565-23634/TryLoveCatch: 3333: DefaultDispatcher-worker-1
通过launch在一个协程中启动子协程。
看一个例子:
private fun launchTest() {
"1111: ${Thread.currentThread().name}".loge("TryLoveCatch")
GlobalScope.launch {
"2222: ${Thread.currentThread().name}".loge("TryLoveCatch")
delay(1000)//1秒无阻塞延迟(默认单位为毫秒)
"3333: ${Thread.currentThread().name}".loge("TryLoveCatch")
//在协程内创建子协程
launch {
"4444: ${Thread.currentThread().name}".loge("TryLoveCatch")
delay(1500)//1.5秒无阻塞延迟(默认单位为毫秒)
"5555: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
"6666: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
"7777: ${Thread.currentThread().name}".loge("TryLoveCatch")
}
结果如下:
2023-06-03 17:00:33.751 26510-26510/TryLoveCatch: 1111: main
2023-06-03 17:00:33.758 26510-26510/TryLoveCatch: 7777: main
2023-06-03 17:00:33.758 26510-26557/TryLoveCatch: 2222: DefaultDispatcher-worker-1
// 1S后
2023-06-03 17:00:34.761 26510-26557/TryLoveCatch: 3333: DefaultDispatcher-worker-1
2023-06-03 17:00:34.762 26510-26557/TryLoveCatch: 6666: DefaultDispatcher-worker-1
2023-06-03 17:00:34.763 26510-26556/TryLoveCatch: 4444: DefaultDispatcher-worker-2
// 1.5S后
2023-06-03 17:00:36.264 26510-26557/TryLoveCatch: 5555: DefaultDispatcher-worker-1
有没有注意到:4444和5555不是一个线程了,这个是为什么呢?我也不清楚。。。
async类似于launch,都是创建一个不会阻塞当前线程的新的协程。它们区别在于:async的返回是Deferred对象,可通过Deffer.await()等待协程执行完成并获取结果。
定义如下:
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
看一个例子:
private fun asyncTest() {
"${Thread.currentThread().name} -> start".loge("TryLoveCatch")
GlobalScope.launch {
val time = measureTimeMillis {//计算执行时间
val deferredOne: Deferred<Int> = async {
delay(2000)
"${Thread.currentThread().name} -> asyncOne".loge("TryLoveCatch")
100//这里返回值为100
}
val deferredTwo: Deferred<Int> = async {
delay(3000)
"${Thread.currentThread().name} -> asyncTwo".loge("TryLoveCatch")
200//这里返回值为200
}
val deferredThr: Deferred<Int> = async {
delay(4000)
"${Thread.currentThread().name} -> asyncThr".loge("TryLoveCatch")
300//这里返回值为300
}
//等待所有需要结果的协程完成获取执行结果
val result = deferredOne.await() + deferredTwo.await() + deferredThr.await()
"${Thread.currentThread().name} -> result == $result".loge("TryLoveCatch")
}
"${Thread.currentThread().name} -> 耗时 $time ms".loge("TryLoveCatch")
}
"${Thread.currentThread().name} -> end".loge("TryLoveCatch")
}
运行结果如下:
2023-06-05 13:38:41.235 30000-30000/TryLoveCatch: main -> start
2023-06-05 13:38:41.240 30000-30000/TryLoveCatch: main -> end
2023-06-05 13:38:43.244 30000-30054/TryLoveCatch: DefaultDispatcher-worker-1 -> asyncOne
2023-06-05 13:38:44.244 30000-30054/TryLoveCatch: DefaultDispatcher-worker-1 -> asyncTwo
2023-06-05 13:38:45.245 30000-30054/TryLoveCatch: DefaultDispatcher-worker-1 -> asyncThr
2023-06-05 13:38:45.246 30000-30054/TryLoveCatch: DefaultDispatcher-worker-1 -> result == 600
2023-06-05 13:38:45.247 30000-30054/TryLoveCatch: DefaultDispatcher-worker-1 -> 耗时 4006 ms
使用async来创建协程,特别需要注意的是:
Job是launch构建协程返回的一个协程任务,完成时是没有返回值的。可以把Job看成协程对象本身,封装了协程中需要执行的代码逻辑,协程的操作方法都在Job身上。Job具有生命周期并且可以取消,它也是上下文元素,继承自CoroutineContext。
Job几个比较重要的函数:
public interface Job : CoroutineContext.Element {
//活跃的,是否仍在执行
public val isActive: Boolean
//启动协程,如果启动了协程,则为true;如果协程已经启动或完成,则为false
public fun start(): Boolean
//取消Job,可通过传入Exception说明具体原因
public fun cancel(cause: CancellationException? = null)
//挂起协程直到此Job完成
public suspend fun join()
//取消任务并等待任务完成,结合了[cancel]和[join]的调用
public suspend fun Job.cancelAndJoin()
//给Job设置一个完成通知,当Job执行完成的时候会同步执行这个函数
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
}
可以看出来,Job跟与Thread很像:
看一个例子,模拟一个无限循环的协程,当协程是活跃状态时每秒钟打印两次消息,1.2秒后取消协程:
private fun jobTest() = runBlocking {
val startTime = System.currentTimeMillis()
// Dispatchers.Default:默认调度器,非主线程。CPU密集型任务调度器,适合处理后台计算。
val job = launch(Dispatchers.Default){
"${Thread.currentThread().name} -> job start".loge("TryLoveCatch")
var nextPrintTime = startTime
var i = 0
while (isActive) {//当job是活跃状态继续执行
if (System.currentTimeMillis() >= nextPrintTime) {//每秒钟打印两次消息
"${Thread.currentThread().name} -> job: I'm sleeping ${i++} ...".loge("TryLoveCatch")
nextPrintTime += 500
}
}
}
"${Thread.currentThread().name} -> 111111".loge("TryLoveCatch")
delay(2000)//延迟2s
"${Thread.currentThread().name} -> 等待2秒后".loge("TryLoveCatch")
//job.join()
//job.cancel()
job.cancelAndJoin()//取消任务并等待任务完成
"${Thread.currentThread().name} -> 协程被取消并等待完成".loge("TryLoveCatch")
}
运行结果如下:
2023-06-05 14:17:47.144 13813-13813/TryLoveCatch: main -> 111111
2023-06-05 14:17:47.144 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job start
2023-06-05 14:17:47.146 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 0 ...
2023-06-05 14:17:47.639 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 1 ...
2023-06-05 14:17:48.139 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 2 ...
2023-06-05 14:17:48.638 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 3 ...
2023-06-05 14:17:49.139 13813-13852/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 4 ...
2023-06-05 14:17:49.147 13813-13813/TryLoveCatch: main -> 等待2秒后
2023-06-05 14:17:49.149 13813-13813/TryLoveCatch: main -> 协程被取消并等待完成
join()是一个挂起函数,它需要等待协程的执行。
去掉delay(2000)
,结果如下:
2023-06-05 14:20:31.230 15044-15081/TryLoveCatch: DefaultDispatcher-worker-1 -> job start
2023-06-05 14:20:31.230 15044-15044/TryLoveCatch: main -> 111111
2023-06-05 14:20:31.231 15044-15081/TryLoveCatch: DefaultDispatcher-worker-1 -> job: I'm sleeping 0 ...
2023-06-05 14:20:31.237 15044-15044/TryLoveCatch: main -> 协程被取消并等待完成
如果去掉job.cancelAndJoin()
,那么子协程会一直运行。
Deferred继承自Job,具有与Job相同的状态机制。它是async构建协程返回的一个协程任务,可通过调用await()方法等待协程执行完成并获取结果。
不同的是Job没有结果值,Deffer有结果值。
public interface Deferred<out T> : Job {
//等待协程执行完成并获取结果
public suspend fun await(): T
}
在说async的时候,已经举过例子了,可以参考下。
协程作用域(CoroutineScope)其实就是为协程定义的作用范围,为了确保所有的协程都会被追踪,Kotlin 不允许在没有使用CoroutineScope的情况下启动新的协程。
协程作用域本质是一个接口:
public interface CoroutineScope {
//此域的上下文。Context被作用域封装,用于在作用域上扩展的协程构建器的实现。
public val coroutineContext: CoroutineContext
}
每个协程生成器launch、async等都是CoroutineScope的扩展。
因为启动协程需要作用域,但是作用域又是在协程创建过程中产生的,这似乎是一个“先有鸡后有蛋还是先有蛋后有鸡”的问题。
官方库给我们提供了一些作用域可以直接来使用:
fun scopeTest() {
//创建一个根协程
GlobalScope.launch {//父协程
launch {//子协程
print("GlobalScope的子协程")
}
launch {//第二个子协程
print("GlobalScope的第二个子协程")
}
}
//为UI组件创建主作用域
val mainScope = MainScope()
mainScope.launch {//启动协程
//todo
}
}
Android 官方对协程的支持是非常友好的,KTX 为 Jetpack 的Lifecycle相关组件提供了已经绑定UV声明周期的作用域供我们直接使用:
在build.gradle添加Lifecycle相应基础组件后,再添加以下组件即可:
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
// 只有Lifecycles(没有 ViewModel 和 LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
因为Activity 实现了LifecycleOwner这个接口,而lifecycleScope则正是它的拓展成员,可以在Activity中直接使用lifecycleScope协程实例:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_data.setOnClickListener {
lifecycleScope.launch {//使用lifecycleScope创建协程
//协程执行体
}
}
}
}
在ViewModel中使用创建协程:
class MainViewModel : ViewModel() {
fun getData() {
viewModelScope.launch {//使用viewModelScope创建协程
//执行协程
}
}
}
官方框架在实现复合协程的过程中也提供了作用域,主要用于明确父子关系,以及取消或者异常处理等方面的传播行为。该作用域分为以下三种:
除了三种作用域中提到的行为以外,父子协程之间还存在以下规则:
调度器确定相应的协程使用那些线程来执行。
CoroutineDispatcher调度器指定指定执行协程的目标载体,它确定了相关的协程在哪个线程或哪些线程上执行。可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
//询问调度器是否需要分发
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
//将可运行块的执行分派到给定上下文中的另一个线程上。这个方法应该保证给定的[block]最终会被调用。
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
//返回一个continuation,它封装了提供的[continuation],拦截了所有的恢复。
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
//CoroutineDispatcher是一个协程上下文元素,而'+'是一个用于协程上下文的集合和操作符。
public operator fun plus(other: CoroutineDispatcher): CoroutineDispatcher = other
}
Kotlin 提供了四个调度器,您可以使用它们来指定应在何处运行协程:
调度器 | 说明 | 场景 |
---|---|---|
Dispatchers.Default | 默认调度器,非主线程。CPU密集型任务调度器,适合处理后台计算。 | 通常处理一些单纯的计算任务,或者执行时间较短任务比如:Json的解析,数据计算等。 |
Dispatchers.Main | UI调度器, Andorid 上的主线程。 | 调度程序是单线程的,通常用于UI交互,刷新等。 |
Dispatchers.Unconfined | 一个不局限于任何特定线程的协程调度程序,即非受限调度器。 | 子协程切换线程代码会运行在原来的线程上,协程在相应的挂起函数使用的任何线程中继续。 |
Dispatchers.IO | IO调度器,非主线程,执行的线程是IO线程。 | 适合执行IO相关操作,比如:网络处理,数据库操作,文件读写等。 |
官方为我们提供了一个withContext顶级函数,在获取数据函数内,调用withContext(Dispatchers.IO)来创建一个在IO线程池中运行的块。您放在该块内的任何代码都始终通过IO调度器执行。由于withContext本身就是一个suspend函数,它会使用协程来保证主线程安全。
//用给定的协程上下文调用指定的挂起块,挂起直到它完成,并返回结果。
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
GlobalScope.launch(Dispatchers.Main) {//开始协程:主线程
val result: User = withContext(Dispatchers.IO) {//网络请求(IO 线程)
userApi.getUserSuspend("FollowExcellence")
}
tv_title.text = result.name //更新 UI(主线程)
}
在主线程中启动一个协程,然后再通过withContext(Dispatchers.IO)调度到IO线程上去做网络请求,获取结果返回后,主线程上的协程就会恢复继续执行,完成UI的更新。
由于withContext可让在不引入回调的情况下控制任何代码行的线程池,因此可以将其应用于非常小的函数,如从数据库中读取数据或执行网络请求。一种不错的做法是使用withContext来确保每个函数都是主线程安全的,那么可以从主线程调用每个函数。调用方也就无需再考虑应该使用哪个线程来执行函数了。您可以使用外部 withContext来让 Kotlin 只切换一次线程,这样可以在多次调用的情况下,以尽可能避免了线程切换所带来的性能损失。
CoroutineContext表示协程上下文,是 Kotlin 协程的一个基本结构单元。
协程上下文主要承载着资源获取,配置管理等工作,是执行环境的通用数据资源的统一管理者。它有很多作用,包括携带参数,拦截协程执行等等。
如何运用协程上下文是至关重要的,以此来实现正确的线程行为、生命周期、异常以及调试。
协程使用以下几种元素集定义协程的行为,它们均继承自CoroutineContext:
协程上下文的数据结构与List和Map非常类似。它包含用户定义的一些数据集合,这些数据与协程密切相关。它是一个有索引的 Element 实例集合。每个 element 在这个集合有一个唯一的Key。
//协程的持久上下文。它是[Element]实例的索引集,这个集合中的每个元素都有一个唯一的[Key]。
public interface CoroutineContext {
//从这个上下文中返回带有给定[key]的元素或null。
public operator fun <E : Element> get(key: Key<E>): E?
//从[initial]值开始累加该上下文的项,并从左到右应用[operation]到当前累加器值和该上下文的每个元素。
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
//返回一个上下文,包含来自这个上下文的元素和来自其他[context]的元素。
public operator fun plus(context: CoroutineContext): CoroutineContext
//返回一个包含来自该上下文的元素的上下文,但不包含指定的[key]元素。
public fun minusKey(key: Key<*>): CoroutineContext
//[CoroutineContext]元素的键。[E]是带有这个键的元素类型。
public interface Key<E : Element>
//[CoroutineContext]的一个元素。协程上下文的一个元素本身就是一个单例上下文。
public interface Element : CoroutineContext {
//这个协程上下文元素的key
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E?
}
}
Element本身也实现了CoroutineContext 接口。
注意:协程上下文的内部实现实际是一个单链表。
CoroutineName是用户用来指定的协程名称的,用于方便调试和定位问题
//用户指定的协程名称。此名称用于调试模式。
public data class CoroutineName(
//定义协程的名字
val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
//CoroutineName实例在协程上下文中的key
public companion object Key : CoroutineContext.Key<CoroutineName>
}
举个例子:
GlobalScope.launch(CoroutineName("GlobalScope")) {
launch(CoroutineName("CoroutineA")) {//指定协程名称
val coroutineName = coroutineContext[CoroutineName]//获取协程名称
print(coroutineName)
}
}
协程内部可以通过coroutineContext这个全局属性直接获取当前协程的上下文。打印数据如下:
[DefaultDispatcher-worker-2] CoroutineName(CoroutineA)
从上面的协程创建的函数中可以看到,协程上下文的参数只有一个,但是怎么传递多个上下文元素呢?CoroutineContext可以使用 " + " 运算符进行合并。由于CoroutineContext是由一组元素组成的,所以加号右侧的元素会覆盖加号左侧的元素,进而组成新创建的CoroutineContext。
感觉CoroutineContext跟Map真的很像!!!
看个例子:
GlobalScope.launch {
//通过+号运算添加多个上下文元素
var context = CoroutineName("协程1") + Dispatchers.Main
print("context == $context")
context += Dispatchers.IO //添加重复Dispatchers元素,Dispatchers.IO 会替换 Dispatchers.Main
print("context == $context")
val contextResult = context.minusKey(context[CoroutineName]!!.key)//移除CoroutineName元素
print("contextResult == $contextResult")
}
注意:如果有重复的元素(key一致)则会右边的会代替左边的元素。打印数据如下:
context == [CoroutineName(协程1), Dispatchers.Main]
context == [CoroutineName(协程1), Dispatchers.IO]
contextResult == Dispatchers.IO
CoroutineStart是一个枚举类,为协程构建器定义启动选项。在协程构建的start参数中使用。
启动模式 | 含义 | 说明 |
---|---|---|
DEFAULT | 默认启动模式,立即根据它的上下文调度协程的执行 | 是立即调度,不是立即执行,DEFAULT 是饿汉式启动,launch 调用后,会立即进入待调度状态,一旦调度器 OK 就可以开始执行。如果协程在执行前被取消,其将直接进入取消响应的状态。 |
LAZY | 懒启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度 | 包括主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,协程将直接进入异常结束状态。 |
ATOMIC | 类似[DEFAULT],以一种不可取消的方式调度协程的执行 | 虽然是立即调度,但其将调度和执行两个步骤合二为一了,就像它的名字一样,其保证调度和执行是原子操作,因此协程也一定会执行。 |
UNDISPATCHED | 类似[ATOMIC],立即执行协程,直到它在当前线程中的第一个挂起点。 | 是立即执行,因此协程一定会执行。即使协程已经被取消,它也会开始执行,但不同之处在于它在同一个线程中开始执行。 |
这些启动模式的设计主要是为了应对某些特殊的场景。业务开发实践中通常使用DEFAULT和LAZY这两个启动模式就够了。
suspend 是 Kotlin 协程最核心的关键字,使用suspend关键字修饰的函数叫作挂起函数,挂起函数只能在协程体内或者在其他挂起函数内调用。否则 IDE 就会提示错误。
协程提供了一种避免阻塞线程并用更简单、更可控的操作替代线程阻塞的方法:协程挂起和恢复。
本质上,挂起函数就是一个提醒作用,函数创建者给函数调用者的提醒,表示这是一个比较耗时的任务,被创建者用suspend标记函数,调用者只需把挂起函数放在协程里面,协程会自动调度处理,完成后在原来的位置恢复执行。
https://rengwuxian.com/kotlin-coroutines-1/
https://rengwuxian.com/kotlin-coroutines-2/
https://rengwuxian.com/kotlin-coroutines-3/
Kotlin 协程实战进阶(一、筑基篇)
Kotlin 协程实战进阶(二、进阶篇)
Kotlin 协程实战进阶(三、原理篇)