美文网首页MVVM + kotlin+ jetpack
5. 遥遥领先 Android kotlin协程原理深入分析,同

5. 遥遥领先 Android kotlin协程原理深入分析,同

作者: 鹏城十八少 | 来源:发表于2024-04-10 17:56 被阅读0次
协程.jpg
1. 协程是啥?
2.进程、线程、协程比较
3.协程的作用
4. 协程 挂起的原理分析:
5. 协程的切线程的原理? 调度原理?
6. 协程的创建与启动
7. 协程的性能优化
8. 协程的线程安全性

9. 协程与RxJava的比较, 可以完全取代rxjava吗?

1. 协程是啥?

协程就是用于 Android 上进行 异步编程 的推荐解决方案,或者说其就是一个 异步框架**

2.进程、线程、协程比较

协程和线程.jpg

Kotlin协程之所以被认为是假协程,是因为它并不在同一个线程运行,而是真的会创建多个线程。

协程是依赖于线程,一个线程中可以创建N个协程,很重要的一点就是协程挂起时不会阻塞线程

线程的性能比协程要差,但是线程池的性能比协程要好!

因为协程底层,最终任务还是需要我们的线程池来承载,但协程还需要维护自己的微型线程,而这个模型又是语言级别的控制,所以当协程代码转为字节码之后,即需要更多的代码才能实现。相比之下,线程池就简单直接很多,故这也是为什么线程池会快一点的原因。

问题: 协程与线程最大的区别在于?

从任务的角度来看,线程一旦开始执行就不会暂停,直到任务结束,这个过程都是连续的。线程之间是抢占式的调度,因此不存在协作问题。

3.协程的作用

面对复杂的业务逻辑,比如多次的异步操作,我们常常会经历回调嵌套的情况,对于开发者而言,无疑苦不堪言

Kotlin协程在Android平台最大的特点,即 简化异步代码! 可以用同步的代码写出异步逻辑

4. 挂起的原理分析:

问题: Kotlin协程为什么能以同步代码写出异步逻辑?

说的,状态机,调度器等等

问题: 在协程上定义了一个局部变量,为什么在其中的其他线程里的协程也能访问到?

协程的概念最核心的点就是函数或者一段程序能够被挂起,稍后再在挂起的位置恢复

4.1 异步场景举例:

  1. 第一步:接口获取当前用户token及用户信息
  2. 第二步:将用户的昵称展示界面上
  3. 第三步:然后再通过这个token获取当前用户的消息未读数
  4. 第四步:并展示在界面上

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()

具体来说

协程在被挂起时,会将当前的执行状态保存到一个回调函数(即挂起函数的 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 kotlinByteCode``

我们通过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 + 状态机

  1. 协程体的执行就是一个状态机,每一次遇到挂起函数,都是一次状态转移,就像我们前面例子中的 label 不断的自增来实现状态流转一样。
  2. 状态机即代码中每一个挂起点和初始挂起点对应的Continuation都会转化为一种状态,协程恢复只是跳转到下一种状态。
  3. 挂起函数将执行过程分为多个 Continuation 片段,并且利用状态机的方式保证各个片段是顺序执行的,所以异步逻辑也可以用顺序的代码来实现

原理:

为什么代码不直接往下执行,而是等io的执行完才执行??

return! 同时是CPS, 生成了很多的中间代码, 导致return后, 不会忘后执行! **

问题: 为什么Kotlin语法要求挂起函数一定要运????

行在协程体内或者其他挂起函数中呢?答案就是,任何一个协程体或者挂起函数中都有一个隐含的Continuation实例,编译器能够对这个

实例进行正确传递,并将这个细节隐藏在协程的背后,让我们的异步代码看起来像同步代码一样


协程.png

5. 协程的切线程的原理? 调度原理?

5.1 总结: 通过封装的线程池,进行线程分发, 执行任务! 通过handler分发主线程

调度原理: 如果理解线程池的工作原理, 差不多!

  • 拦截器在每次(恢复)执行协程体的时候都会拦截协程本体SuspendLambda,然后会通过协程分发器的 interceptContinuation() 方法拦截了一个Continuation<T>并且再返回一个Continuation<T>。拦截到Continuation后就可以做一些事情,比如线程切换等。
  • 把拦截的代码块封装为任务 DispatchedContinuation ,会通过 CoroutineDispatcherneedDispatch() 来判断需不需要分发,由子类的 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绑定到这个LifecycleOwnerLifecycle 。当宿主被销毁时,这个作用域也被取消。

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:协程启动模式,这些启动模式的设计主要是为了应对某些特殊的场景。业务开发实践中通常使用DEFAULTLAZY这两个启动模式就够了。

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 )启动协程的代码可以分为三部分:GlobalScopelaunchDispatchers,它们分别对应:协程的作用域、构建器和调度器.

源码分析:

CoroutineContext、协程上下文, Job, CoroutineDispatcher, ContinuationInterceptor 等都是 CoroutineContext 的子类

Job:任务

Job 是协程的句柄,过Job实现对协程的控制和管理。

Joblaunch构建协程返回的一个协程任务,完成时是没有返回值的.

协程的操作方法都在Job身上。Job具有生命周期并且可以取消.

一个任务可以包含一系列状态: 新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问 Job 的属性: isActiveisCancelledisCompleted

                                      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吗?

相关文章

网友评论

    本文标题:5. 遥遥领先 Android kotlin协程原理深入分析,同

    本文链接:https://www.haomeiwen.com/subject/igoqxjtx.html