「Kotlin篇」原来,协程是这么挂起的

本文深入探讨Kotlin协程的挂起和恢复机制,通过Continuation接口、invokeSuspend方法以及挂起函数的工作原理,解释了协程如何在非阻塞情况下实现挂起和恢复。通过实例分析,展示了从启动协程到执行挂起函数的过程,揭示了协程在Android应用中的具体应用和优势。
摘要由CSDN通过智能技术生成

协程这个概念已经出来很长时间了,网上对它的定义是非阻塞式的线程框架,讨论最多的也是协程的挂起、恢复以及线程切换,那到底挂起是个什么样的概念,怎么就挂起了,怎么就又恢复了?

带着这些问题,我走上了不归路…

在开始探索协程挂起、恢复之前,需要先了解一下几个重要的名词和概念。

1. Continuation

Continuation在协程中其实只是一个接口,其作用有点类似RxJava中Observer,当请求成功时,触发onNext继续更新UI或者下一步的操作。只不过在协程中,Continuation包装了协程在挂起之后应该继续执行的代码,在编译的过程中,一个完整的协程被分割切块成一个又一个续体。在 await 函数的挂起结束以后,它会调用 continuation 参数的 resumeWith 函数,来恢复执行 await 函数后面的代码。

2. invokeSuspend

invokeSuspend中包含的便是我们协程体中的代码,内部管理了一个状态值,循环触发invokeSuspend的调用,而后根据不同的状态,做协程的挂起和恢复操作。

这两个名词虽然比较抽象,但是对于后面的分析还是比较重要的,还是先以一个简单的例子开始,慢慢理解。

使用launch开启一个协程,在协程体中,加入两个挂起函数,loadDataAloadDataB,并且在函数前后,log打印出函数的运行轨迹。如下:

Log.d(TAG, "onCreate: start")
lifecycleScope.launch {
    val num = loadDataA()
    loadDataB(num)
}
Log.d(TAG, "onCreate: end") 
private suspend fun loadDataA():Int {
    delay(3000)
    Log.d(TAG, "loadDataA: ")
    return 1
}
​
private suspend fun loadDataB(num:Int) {
    delay(1000)
    Log.d(TAG, "loadDataB: $num")
} 

那launch开启协程,内部做了些什么?又是如何处理loadDataA和loadDataB这些挂起函数的?

不妨先在launch处打上断点,Debug看看它的执行路径。

image.png

如上图所示,从onCreate开始,一个lifecycleScope.launch 的执行顺序是

launch->start->invoke->startCoroutineCancellable->resumeWith再到最后invokeSuspend方法,

至于invokeSuspend的作用是什么?这个后面会详细说明。

既然给了大致的执行方向,我们只需要一步一步的跟进,查看内部详细的代码处理。

# Builders.common.kt
    
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
} 

launch开始,在不阻塞当前线程的情况下启动一个新的协程,并将对协程的引用作为Job 返回,CoroutineScope.launch中传入了三个参数,第一个CoroutineContext为协程的上下文,第二个CoroutineStart,协程启动选项。 默认值为CoroutineStart.DEFAULT ,第三个block即协程体中的代码,也就是上面例子中的loadDataAloadDataB

接着coroutine.start(start, coroutine, block)使用给定的代码块和启动策略启动此协程。

# AbstractCoroutine.kt
    
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    initParentJob()
    start(block, receiver, this)
} 

initParentJob()方法之后,调用了start,这个时候你会发现,已经无法再继续跟进去了。

但是在文章一开始Debug的路径显示,在start后,执行到的是CoroutineStart#invoke()

# CoroutineStart.kt
    
@InternalCoroutinesApi
public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>): Unit =
    when (this) {
        DEFAULT -> block.startCoroutineCancellable(completion)
        ATOMIC -> block.startCoroutine(completion)
        UNDISPATCHED -> block.startCoroutineUndispatched(completion)
        LAZY -> Unit // will start lazily
    } 

在invoke中使用此协程的启动策略将相应的块作为协程启动,这里以DEFAULT为例,startCoroutineCancellable(),而在startCoroutineCancellable中,创建了Continuation,且调用resumeWith来传递请求结果。

# DispatchedContinuation.kt

public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    else -> resumeWith(result)
} 

到这里,你可能还是云里雾里,跟进这么多方法,到底有什么用途?不急

我们接着往下看核心部分Continuation

public interface Continuation<in T> {
    public val
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值