1. 协程是啥?
2.进程、线程、协程比较
3.协程的作用
4. 协程 挂起的原理分析:
5. 协程的切线程的原理? 调度原理?
6. 协程的创建与启动
7. 协程的性能优化
8. 协程的线程安全性
9. 协程与RxJava的比较, 可以完全取代rxjava吗?
1. 协程是啥?
协程就是用于 Android
上进行 异步编程 的推荐解决方案,或者说其就是一个 异步框架**
2.进程、线程、协程比较
协程和线程.jpgKotlin协程之所以被认为是假协程,是因为它并不在同一个线程运行,而是真的会创建多个线程。
协程是依赖于线程,一个线程中可以创建N个协程,很重要的一点就是协程挂起时不会阻塞线程
线程的性能比协程要差,但是线程池的性能比协程要好!
因为协程底层,最终任务还是需要我们的线程池来承载,但协程还需要维护自己的微型线程,而这个模型又是语言级别的控制,所以当协程代码转为字节码之后,即需要更多的代码才能实现。相比之下,线程池就简单直接很多,故这也是为什么线程池会快一点的原因。
问题: 协程与线程最大的区别在于?
从任务的角度来看,线程一旦开始执行就不会暂停,直到任务结束,这个过程都是连续的。线程之间是抢占式的调度,因此不存在协作问题。
3.协程的作用
面对复杂的业务逻辑,比如多次的异步操作,我们常常会经历回调嵌套的情况,对于开发者而言,无疑苦不堪言
Kotlin协程在Android平台最大的特点,即 简化异步代码! 可以用同步的代码写出异步逻辑
4. 挂起的原理分析:
问题: Kotlin协程为什么能以同步代码写出异步逻辑?
说的,状态机,调度器等等
问题: 在协程上定义了一个局部变量,为什么在其中的其他线程里的协程也能访问到?
协程的概念最核心的点就是函数或者一段程序能够被挂起,稍后再在挂起的位置恢复
4.1 异步场景举例:
- 第一步:接口获取当前用户token及用户信息
- 第二步:将用户的昵称展示界面上
- 第三步:然后再通过这个token获取当前用户的消息未读数
- 第四步:并展示在界面上
demo1:
apiService.getUserInfo().enqueue(object :Callback<User>{
override fun onResponse(call: Call<User>, response: Response<User>) {
val user = response.body()
tvNickName.text = user?.nickName
apiService.getUnReadMsgCount(user?.token).enqueue(object :Callback<Int>{
override fun onResponse(call: Call<Int>, response: Response<Int>) {
val tvUnReadMsgCount = response.body()
tvMsgCount.text = tvUnReadMsgCount.toString()
}
})
}
})
挂起(非阻塞式挂起)
协程的挂起.jpg每一次从主线程切到IO线程都是一次协程的挂起操作;
每一次从IO线程切换主线程都是一次协程的恢复操作;
4.2 问题: 协程挂起是挂起什么?
挂起和恢复是suspend函数特有的能力,其他函数不具备,挂起的内容是协程,不是挂起线程,也不是挂起函数,
当线程执行到suspend函数的地方,不会继续执行当前协程的代码了,所以它不会阻塞线程,是非阻塞式挂起。
问题: suspend函数作用?
suspend: 提醒是耗时函数
那么: suspend函数就会在子线程种执行么? 不对,而是根据withContext, 调度器! 不过是在这个 suspend 函数指定的线程里执行。
紧接着在 suspend 函数执行完成之后,协程会自动帮我们把线程再切回来
withcontext: 是一个suspend函数, 所以它也要在suspend函数中声明!
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
coroutineScope.launch(Dispatchers.Main) {
val message = getNetMessages()
showMessage(message)
}
suspend fun getNetMessages(): String {
return withContext(Dispatchers.IO) {
delay(1000)
"123"
}
}
4.3 使用的3步
1.coroutineScope.launch{} 启动协程
2.withContext。 调度器
3.suspend。 挂起函数
代码的运行顺序:
-
1). 当我们的程序运行到
coroutineScope.launch(Dispatchers.Main)
时,此时会创建一个新协程,并将这个协程放入默认的协程调度器(即Main调度器),同时当前新创建的协程也会成为coroutineScope
的子协程。* 2). 当执行到getNetMssage()
方法时,此时遇到了withContext(Dispatchers.IO)
,此时会切换当前协程的上下文到IO调度器(可以理解将当前协程放入IO线程池中执行),此时协程将被挂起,然后我们当前withContext()
被挂起的状态会通知给外部的调用者,并将当前的状态保存到协程的上下文中,直到IO操作完成。- 3).当遇到
delay(1000)
时,此时再次挂起(这里不是切换线程,而是使用了协程的调度算法),并保存当前的函数状态; - 4). 当
delay(1000)
结束后,再次恢复到先前所在的IO调度器,并开始返回 “123”; - 5). 当上述逻辑执行完成后,此时
withContext()
会将协程的调度器再次切换到之前开始时的调度器(这里是Main),并恢复之前的函数状态;
此时我们获得了
getNetMssage()
的返回值,继续执行showMessage()
。 - 3).当遇到
具体来说
协程在被挂起时,会将当前的执行状态保存到一个回调函数(即挂起函数的 Continuation)中,然后将控制权交回给调用方。当协程准备好恢复时,它会从回调函数中取回执行状态,继续执行
4.4 总的原理: CPS + 状态机
CPS转换
Kotlin 的协程是依靠编译器实现的,
Java中没有suspend函数,suspend是Kotlin中特有的关键字,当编译时,Kotlin编译器会将含有suspend关键字的函数进行一次转换。这个过程又被称作 CPS转换, (cotinuation-passing-style)。
CPS: Continuation Passing Style(续体传递风格): 约定一种编程规范,函数不直接返回结果值,而是在函数最后一个参数位置传入一个 callback 函数参数,并在函数执行完成时通过 callback 来处理结果。回调函数 callback 被称为续体(Continuation),它决定了程序接下来的行为,整个程序的逻辑通过一个个 Continuation 拼接在一起。
这就是咱们常说的用看起来同步的方式写出异步的代码,消除回调地狱(callback hell)
状态机: ** 另外为了避免栈空间过大的问题, Kotlin 编译器并没有把代码转换成函数回调的形式,而是利用状态机模型**。每两个挂起点之间可以看为一个状态,每次进入状态机时都有一个当前的状态,然后执行该状态对应的代码;如果程序执行完毕则返回结果值,否则返回一个特殊值,表示从这个状态退出并等待下次进入。相当于创建了一个可复用的回调,每次都使用这同一个回调,根据不同状态来执行不同的代码。
4.5 协程挂起的理解:
写的挂起函数代码:
suspend fun getUserInfo() : User {
val user = User("asd123", "userName", "nickName")
return user
}
java中间态代码(便于理解)
fun getUserInfo(callback: Callback<User>): Any? {
val user = User("asd123", "userName", "nickName")
callback.onSuccess(user)
return Unit
}
转换后的代码:
fun getUserInfo(cont: Continuation<User>): Any? {
val user = User("asd123", "userName", "nickName")
cont.resume(user)
return Unit
}
第一步:将上面的挂起函数解析成字节码:通过AS的工具栏中Tools
->kotlin
->show kotlin
ByteCode``
我们通过Kotlin生成字节码工具查看字节码,然后点击反编译的按钮将其反编译成Java代码:
java代码和字节码的区别:
@Nullable
public final Object getUserInfo(@NotNull Continuation $completion) { //它还把返回值 User改成了 Object
User user = new User("asd123", "userName", "nickName");
return user;
}
Continuation:(重点)
Continuation是 Kotlin 协程中非常重要的一个概念,它表示
一个挂起点之后的延续操作
Continuation是怎么创建的?
Continuation
很有可能是从协程充传入来的,查看协程构建的源码:
public fun CoroutineScope.launch(): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
确实是会通过引入一个Continuation对象来实现恢复的流程,这里的这个Continuation对象中包含了Callback的形态。Continuation
类似于网络请求回调Callback
,
挂起函数的时候需要传递一个Continuation
给它,只是传递这个参数是由编译器悄悄传,而不是我们传递的
它有两个作用:1. 暂停并记住执行点位;2. 记住函数暂停时刻的局部变量上下文。
1). 方法resumeWith ; 恢复, 拿到结果后, 恢复协程, 继续往下执行!
2). 方法invokeSuspend: 拿到suspend方法结果,比如: requestUserInfo()
的返回值
2者的关系如下源码:
resumeWith
方法内部会调用 invokeSuspend
方法
// BaseContinuationImpl
public final override fun resumeWith(result: Result<Any?>) {
// ...
val outcome = invokeSuspend(param)
// ...
}
4.6 重要的类的关系:
协程存在着三层包装
- Continuation: 续体,恢复协程的执行
- BaseContinuationImpl: 实现 resumeWith(Result) 方法,控制状态机的执行,定义了 invokeSuspend 抽象方法
- ContinuationImpl: 增加 intercepted 拦截器,实现线程调度等
- SuspendLambda: 封装协程体代码块
- 协程体代码块生成的子类: 实现 invokeSuspend 方法,其内实现状态机流转逻辑
BaseContinuationImpl 类里面:
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) { // 一直循环
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
/// invokeSuspend() 执行续体下一个状态的逻辑
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
协程启动后会调用到上面这个 resumeWith() 方法, 接着调用其 invokeSuspend() 方法:
挂起: 当 invokeSuspend() 返回 COROUTINE_SUSPENDED 后,就直接 return 终止执行了,此时协程被挂起。* 执行完毕: 当 invokeSuspend() 返回非 COROUTINE_SUSPENDED 后,说明协程体执行完毕了,对于 launch 启动的协程体,传入的 completion 是 AbstractCoroutine 子类对象,最终会调用其 AbstractCoroutine.resumeWith() 方法做一些状态改变之类的收尾逻辑。至此协程便执行完毕了。
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
internal abstract class BaseContinuationImpl() : Continuation<Any?> {
public final override fun resumeWith(result: Result<Any?>) { // 重要的函数
//省略好多代码
invokeSuspend()
//省略好多代码
}
protected abstract fun invokeSuspend(result: Result<Any?>): Any? // 核心函数
}
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
//invokeSuspend() 这个方法是恢复的关键一步
4.7 协程状态机的源码分析:
有2个都要看源码
1). 挂起函数的 (协程的恢复)
2). 启动协程的整个业务逻辑: (协程的挂起)
反编译它来阐述协程状态机的原理,逆向剖析协程的挂起和恢复。
invokeSuspend{ // 这个方法
loop = true
while(loop) {
when (continuation.label) { // label值, 记录位置
0 -> {
// 检测异常
throwOnFailure(result)
// 将 label 置为 1,准备进入下一次状态
continuation.label = 1
// 执行 getUserInfoSuspend(第一个挂起函数)
suspendReturn = getUserInfoSuspend(continuation)
// 当第一个suspend方法执行完成,会回调Continuation的invokeSuspend方法
// 判断是否挂起
if (suspendReturn == sFlag) {
return suspendReturn
} else {
result = suspendReturn
//go to next state
}
}
1 -> {
throwOnFailure(result)
// 获取 user 值
user = result as Any
// 准备进入下一个状态
continuation.label = 2
// 执行 getUnReadMsgCountSuspend
suspendReturn = getUnReadMsgCountSuspend(user.token, continuation)
// 判断是否挂起
if (suspendReturn == sFlag) { // suspend方法执行
return suspendReturn
} else {
result = suspendReturn // 直接拿到结果
//go to next state
}
}
2 -> {
throwOnFailure(result)
user = continuation.mUser as Any
unReadMsgCount = continuation.unReadMsgCount as Int
loop = false
}
}
4.7.1 )挂起点:
一个挂起函数
要挂起,那么它必定得有一个挂起点
,不然无法知道函数是否挂起,从哪挂起呢?
状态机:
问题: 为什么用状态机?
比如当触发挂起函数调用时,会进入其内部对应的状态机,从而触发状态流转。并且为了避免了 callback 的 重复创建,而每一个挂起函数内部都会复用当前已创建好的
case 0; 第一次进入
case 1: 第二次进入
lable: 每次要执行挂起函数前,通过将 label
递增的形式来将挂起点前后的代码进行切片。由此来实现每一次调用 invokeSuspend
方法时,精准的控制代码的执行起点。
详细解释如下:
case 0; 相当于挂起: 首先执行第一个suspend方法并传递当前Continuation,得到返回值,返回COROUTINE SUSPENDED,协程框架就直接return, (如果不是suspend方法,就不会挂起,执行另外一个地方), 则 launch 协程体中 getId 后面的代码暂时不会执行。这里将 label 置为了 1. 这时候挂起!
继续: 当第一个suspend方法执行完成,得到想要的结果, 就会回调Continuation的invokeSuspend方法(在自己的suspend方法里面调用), 然后通过resumeWith()!这里又调用了一次自己
case 1: 第二次就行入这个时候进
每一个挂起点和初始挂起点对应的 Continuation 都会转化为一种状态,协程恢复只是跳转到下一种状态中。
挂起函数将执行过程分为多个 Continuation 片段,并且利用状态机的方式保证各个片段是顺序执行的
4.7.2). 挂起和恢复的具体操作:
挂起(暂停)协程时,会复制并保存当前的堆栈帧以供稍后使用,将信息保存到Continuation
对象中。
恢复协程时,会将堆栈帧从其保存位置复制回来,对应的Continuation
通过调用resumeWith
函数才会恢复协程的执行,然后函数再次开始运行。同时返回Result<T>
类型的成功或者异常的结果。
4.7.3). 挂起和恢复的本质:
协程的挂起本质上是方法的挂起,而方法的挂起本质上是 return
,协程的恢复本质上方法的恢复,而恢复的本质是 callback
回调。
挂起函数:
注意: ****被 suspend
修饰的函数不一定能导致协程被挂起,还需要里面的实现经过编译之后有返回值并且为 COROUTINE_SUSPENDED
才可以!
不会挂起的挂起函数
里我们定义了两个挂起函数,一个会真正挂起,一个会直接返回结果
小结:协程的挂起和恢复的本质是CPS + 状态机
- 协程体的执行就是一个状态机,每一次遇到挂起函数,都是一次状态转移,就像我们前面例子中的 label 不断的自增来实现状态流转一样。
- 状态机即代码中每一个挂起点和初始挂起点对应的Continuation都会转化为一种状态,协程恢复只是跳转到下一种状态。
- 挂起函数将执行过程分为多个 Continuation 片段,并且利用状态机的方式保证各个片段是顺序执行的,所以异步逻辑也可以用顺序的代码来实现
原理:
为什么代码不直接往下执行,而是等io的执行完才执行??
return! 同时是CPS, 生成了很多的中间代码, 导致return后, 不会忘后执行! **
问题: 为什么Kotlin语法要求挂起函数一定要运????
行在协程体内或者其他挂起函数中呢?答案就是,任何一个协程体或者挂起函数中都有一个隐含的Continuation实例,编译器能够对这个
实例进行正确传递,并将这个细节隐藏在协程的背后,让我们的异步代码看起来像同步代码一样
协程.png
5. 协程的切线程的原理? 调度原理?
5.1 总结: 通过封装的线程池,进行线程分发, 执行任务! 通过handler分发主线程
调度原理: 如果理解线程池的工作原理, 差不多!
- 拦截器在每次(恢复)执行协程体的时候都会拦截协程本体
SuspendLambda
,然后会通过协程分发器的interceptContinuation()
方法拦截了一个Continuation<T>
并且再返回一个Continuation<T>
。拦截到Continuation
后就可以做一些事情,比如线程切换等。 - 把拦截的代码块封装为任务
DispatchedContinuation
,会通过CoroutineDispatcher
的needDispatch()
来判断需不需要分发,由子类的dispatch(runnable)
方法来实现协程的本次调度工作
切换线程:
根据创建协程指定调度器HandlerContext
,DefaultScheduler
,UnconfinedDispatcher
来执行任务,以解决协程中的代码运行在那个线程上。HandlerContext
通过handler.post(runnable)
分发到主线程,DefaultScheduler
本质是通过excutor.excute(runnable)
分发到IO线程。
CoroutineDispatcher、--调度器,是一个标准库中帮我们封装了切换线程的帮助类,可以简单理解为一个线程池
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
//是否需要调度
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
//将可运行块的执行分派到给定上下文中的另一个线程上
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
//通过DispatchedContinuation返回包装原始continuation的 continuation
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
}
5.2. 拦截器: ContinuationInterceptor
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
// 如果是ContinuationImpl类型,则调用intercepted方法,否则返回自身
// 这里的 this 是 Main$main$1 实例 - ContinuationImpl的子类
(this as? ContinuationImpl)?.intercepted() ?: this
// ContinuationImpl
public fun intercepted(): Continuation<Any?> =
// context[ContinuationInterceptor]是 CoroutineDispatcher 实例
// 需要线程调度 - 返回 DispatchedContinuation,其 continuation 参数值为 SuspendLambda
// 不需要线程调度 - 返回 SuspendLambda
intercepted ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this).also { intercepted = it }
// CoroutineDispatcher
// continuation - SuspendLambda -> ContinuationImpl -> BaseContinuationImpl
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
BaseContinuationImpl类的核心方法!
@SinceKotlin("1.3")
internal abstract class BaseContinuationImpl(
// This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
// it has a public getter (since even untrusted code is allowed to inspect its call stack).
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
5.3 ) 调度器模式
Kotlin 提供了四个调度器,您可以使用它们来指定应在何处运行协程:
和线程池一样, 有多种模式! 主线程, 子线程, 默认!
CoroutineStart
、协程启动模式, 有4种启动模式! DEFAULT是饿汉式启动,launch调用后,会立即进入待调度状态,一旦调度器OK就可以开始执行。
CoroutineScope
。 协程作用域
5.4 ) 协程作用域(CoroutineScope
)
其实就是为协程定义的作用范围每个协程生成器launch、async等都是CoroutineScope的扩展,并继承了它的coroutineContext自动传播其所有元素和取消。协程作用域本质是一个接口:
Async 也是一个作用于 CoroutineScope 的扩展函数,和 launch 的区别主要就在于:async 可以返回协程的执行结果,而 launch 不行!
async相当于线程里面的call, further, async和await() 是一对出现的!
列子如下:
fun asyncTest() {
mScope.launch {
// 开启一个IO模式的线程 并返回一个Deferred,Deferred可以用来获取返回值
// 代码执行到此处时会新开一个协程 然后去执行协程体 父协程的代码会接着往下走
val deferred = async(Dispatchers.IO) {
// 模拟耗时
delay(2000)
// 返回一个值
"Quyunshuo"
}
// 等待async执行完成获取返回值 此处并不会阻塞线程 而是挂起 将线程的执行权交出去
// 等到async的协程体执行完毕后 会恢复协程继续往下执行
val date = deferred.await()
}
}
lifecycleScope:
为了确保所有的协程都会被追踪,Kotlin 不允许在没有使用CoroutineScope
的情况下启动新的协程。 lifecycleScope通过lifecycle
,SupervisorJob()
,Dispatchers.Main
创建一个LifecycleCoroutineScopeImpl
,它是一个关联宿主生命周期的作用域。
CoroutineScope
绑定到这个LifecycleOwner
的Lifecycle
。当宿主被销毁时,这个作用域也被取消。
5种作用域:
1).GlobalScope 全局作用域, 不推荐使用
2). runBlocking 这个顶层函数来启动协程, 主要用于测试
3). coroutineScope
函数用于创建一个独立的协程作用域
4). supervisorScope
函数用于创建一个使用了 SupervisorJob 的 coroutineScope,该作用域的特点就是抛出的异常不会连锁取消同级协程和父协程
5). 自定义 CoroutineScope
6). LifecycleOwner.lifecycleScope - 推荐使用
ViewModel.viewModelScope - 推荐使用
withContext: 是一个Suspend函数,可用于在协程中切换上下文(线程)。它允许您在不阻塞UI线程的情况下执行长时间运行的代码,并将结果返回到主线程。
6.1 协程启动的流程
1). 我们通过 suspend block#createCoroutine 得到的 coroutine 实际是 SafeContinuation 对象
2). SafeContinuation 实际上是代理类,其中的 delegate 属性才是真正的 Continuation 对象
3). suspend block 中的代码在 BaseContinuationImpl 中执行
4). 我们的匿名内部类对象 Continuation 被回调
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block) // 启动
return coroutine
}
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion) // 启动
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
6.2 创建协程这里介绍常用的两种方式
1). runBlocking:T
:顶层函数,创建一个新的协程同时阻塞当前线程,直到其内部所有逻辑以及子协程所有逻辑全部执行完成,返回值是泛型T
,一般在项目中不会使用,主要是为main函数和测试设计的
2). 没有返回值, CoroutineScope.launch() 创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用。它返回的是一个该协程任务的引用,即Job
对象。这是最常用的用于启动协程的方式。(用的最多的)
3). 有返回值, CoroutineScope.async(). 创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用。并返回Deffer
对象,可通过调用Deffer.await()
方法等待该子协程执行完成并获取结果。常用于并发执行-同步等待和获取返回值的情况
launch: 返回一个job对象
launch
是最常用的用于启动协程的方式,用于在不阻塞当前线程的情况下启动一个协程,并返回对该协程任务的引用,即Job
对象。
launch的四大元素:
****1). context:** 协程的上下文,表示协程的运行环境,包括协程调度器、代表协程本身的Job、协程名称、协程ID等,默认值是当前线程上的事件循环
2). start:协程启动模式,这些启动模式的设计主要是为了应对某些特殊的场景。业务开发实践中通常使用DEFAULT和LAZY这两个启动模式就够了。
3). block: 协程代码,它将在提供的范围的上下文中被调用。它是一个用suspend
(挂起函数)关键字修饰的一个无参,无返回值的函数类型。接收者是CoroutineScope
的函数字面量。
4). Job: 协程构建函数的返回值,可以把Job
看成协程对象本身,封装了协程中需要执行的代码逻辑,是协程的唯一标识,Job可以取消,并且负责管理协程的生命周期。 Job 完成时是没有返回值的,如果需要返回值的话,应该使用 Deferred**
GlobalScope.launch(Dispatchers.Main) {//开始协程:主线程
val result = userApi.getUserSuspend("suming")//网络请求(IO 线程)
tv_name.text = result?.name //更新 UI(主线程)
}
上面就是启动协程的代码,
6.3 )启动协程的代码可以分为三部分:GlobalScope
、launch
、Dispatchers
,它们分别对应:协程的作用域、构建器和调度器.
源码分析:
CoroutineContext
、协程上下文, Job, CoroutineDispatcher, ContinuationInterceptor 等都是 CoroutineContext 的子类
Job:
任务
Job
是协程的句柄,过Job
实现对协程的控制和管理。
Job
是launch
构建协程返回的一个协程任务,完成时是没有返回值的.
协程的操作方法都在Job
身上。Job
具有生命周期并且可以取消.
一个任务可以包含一系列状态: 新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问 Job
的属性: isActive
、isCancelled
和 isCompleted
。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
6.4) 协程的启动总结:
协程的启动是通过 BaseContinuationImpl.resumeWith ()方法调用到了子类 SuspendLambda.invokeSuspend 方法
7. 协程的性能优化
问题: 如何优化协程的性能?在什么情况下应该考虑使用协程池?
出发点: 考察日常使用协程时的注意点与优化策略
参考简答: 协程性能优化的方法包括:
调整调度器: 使用Dispatchers.Default适用于CPU密集型操作,而Dispatchers.IO适用于I/O密集型操作,合理选择调度器可以提高性能。
使用asContextElement: 可以通过将协程调度器设置为Unconfined,让协程在不同线程之间自由切换,适用于轻量级任务。
协程池的使用:
协程池的创建: 可以使用newFixedThreadPoolContext等函数创建协程池,控制并发数量。
适用场景: 在大量并发任务的情况下,使用协程池可以避免创建过多的线程,提高性能。
8. 协程的线程安全性
问题: 如何确保协程中的数据操作是线程安全的?在协程中有哪些工具可以使用?
出发点: 对协程安全的认知,可以介绍具体的使用方式,通过哪些类或者方法来确保线程安全。
参考简答: 确保协程中线程安全的方法包括:
使用Mutex: 可以通过Mutex来实现协程中的临界区,保护共享数据的访问。
使用withContext: 通过在协程中使用withContext函数,将代码块切换到指定的线程上,避免多线程访问共享数据。
使用Atomic类: 对于简单的原子操作,可以使用Atomic类来保障线程安全。
9. 协程与RxJava的比较
问题: 协程和RxJava在异步编程中有什么异同?在什么情况下更适合使用协程或RxJava?
出发点: 可以从语法、错误处理等方面展开,适用场景可以根据各自的优点进行应用。
参考简答: 协程和RxJava的异同点:
语法: 协程更贴近传统的同步代码,使用async/await等语法,而RxJava使用链式调用的方式。
错误处理: 协程使用try-catch块捕获异常,而RxJava使用onError处理错误。
背压: RxJava有背压策略来处理生产者和消费者之间的速度不一致,而协程可以通过挂起来实现类似的效果。
适用场景:
协程: 更适合简单的异步任务,对于并发性能要求不是很高的场景。
RxJava: 适用于复杂的异步任务,需要处理背压和具备更多操作符的场景。
协程可用可以完全取代rxjava吗?
网友评论