Kotlin 协程 - 挂起函数 Suspend Function

Kotlin 2. Kotlin Suspend 挂起函数 这里介绍 Kotlin Suspend 函数的使用。挂起(suspend)函数是所有协程的核心。 挂起函数可以执行长时间运行的操作并等待它完成而不会阻塞主线程。Kotlin 的 suspend 关键字可以帮助我们消除回调,用同步的写法写异步。 阅读详情
AI摘要

本文深入解析Kotlin协程中挂起函数的核心机制,包括挂起与恢复过程、非阻塞特性、suspend关键字的编译转换、Continuation续体接口及其实现类、有限状态机(FSM)在协程中的建模方式(含红绿灯类比)、标签驱动的状态跳转逻辑,以及自定义挂起函数的两种实现路径(封装与回调改造)。重点阐述挂起函数如何通过状态机+Continuation实现线程无阻塞切换与上下文保存。

参考文章1

参考文章2

一、概念

  • 函数类型:suspend () → Unit
  • 挂起:挂起函数挂起的是父协程,让协程和线程分离让出线程,等挂起函数执行完返回到父协程中,恢复在之前的线程上执行后续代码。
  • 本质:暂停当前任务,中途去做其它事情,做完后回来继续。同样是切线程做其他事情,只是做完了可以回来继续做之前的事。
  • 作用:和普通函数封装功能一样,挂起函数通常被放到其他线程中执行,能更方便的指定线程而不用担心调用时出现切换问题。
  • 限制:挂起函数“挂起恢复”的特性只能在协程环境下实现,因此只能在其它挂起函数或协程中被调用,创建的只能是子协程。
  • 原理:协程会被编译成 switch 语句(状态机模式),每个挂起函数都是其中的一个 case 分支,每个 case 依赖前面的 case,这就是协程切换与挂起停止的原理。

1.1 挂起恢复的过程

①挂起函数挂起的是父协程。

  • 此时被挂起的父协程:代码不会继续往下执行(即写在挂起函数下面的那些,而挂起函数上面的代码没执行完的话,挂起函数指定在相同线程执行会等挂起函数执行完再执行,指定在其它线程则继续并发执行)。
  • 此时被挂起的父协程所在的线程:不会阻塞而是脱离当前任务去执行其他任务或无事可干(就是它原本受系统调度的样子,回收或再利用)。

②父协程被挂起后,挂起函数在它指定的线程中执行自己的代码。

③挂起函数执行完后,携带结果恢复到父协程中,父协程从之前的进度继续往下执行。

1.2 非阻塞式

        单线程是阻塞式的,当前任务没做完就不会执行后面的代码,多线程是切到别的线程执行就不会阻塞之前的线程。

        对比其它基于 Java 的多线程解决方案,协程借助 Kotlin 语言简洁的优势以及“挂起恢复”的特性消除了回调嵌套,即原先串行写的代码现在并行来写(让串行嵌套的异步代码像同步那样并行编写),逻辑直观并消除模板代码(Java切线程会回调里嵌套回调),降低了多线程异步之间协作任务的操作难度(调用起来不用考虑任务是执行在哪个线程)。

1.3 suspend 关键字

suspend关键字在编译后消失了,取而代之的是编译器将挂起函数转换为了一个带有 Continuation参数的普通函数,并且函数内部被重构为一个有限状态机(Finite State Machine)。

  • 签名变化:suspend(Int) -> Boolean 变成 (Int, Continuation<Boolean>) -> Any?
  • 逻辑变化:线性的同步代码 基于 label跳转的 switch-case(或 when)
  • 状态机结构。运行效果:实现了“挂起”而不阻塞线程,通过回调(Continuation)恢复执行。
编码阶段用来限制该函数只能在协程环境中被调用,因为“挂起恢复”只有在协程环境中才能实现。由于挂起函数通常被拿来包装子线程耗时任务,因此也有该提醒的意思。在函数体中调用了由协程库提供的挂起函数,才会使用到 continuation 参数,suspend 关键字才不会多余,不然 AndroidStudio 会提示可去掉。
编译阶段由 suspend 修饰的函数编译后会增加一个形参 Continuation(续体)。当调用其它挂起函数的时候,会将当前协程作为 Continuation 参数进行传递(因为所有协程都是 AbstractContinuation 的子类,而它继承了 Continuation、Job、CoroutineScope,因此不管调用方是协程作用域对象、协程构建器、挂起函数,都是协程续体可传递给被调用的挂起函数),这样就形成了一个调用链,最深层的挂起函数执行完会恢复到上层,以此类推直到整个调用链恢复完毕,因此挂起函数只能在协程环境中被调用。编译后的函数体中就可以通过该续体对象调用 resume 系列函数来恢复到父协程(函数不再返回结果而是接收一个续体参数并通过续体携带结果返回执行后续逻辑)或取消当前协程,这就是CPS(Continuation-Passing Style)续体传递方式。
suspend fun show(num: Int): Boolean
//编译后
//增加参数 continuation,原返回值类型 Boolean 成为 Continution<Boolean> 持有的泛型
//返回值 Any? 是由于父协程是否挂起返回的东西不同:
    //挂起返回的是枚举值 COROUTINE_SUSPEND
    //未挂起返回的是结果值的类型或异常,详见 getResult() 源码
fun show(num: Int, continuation: Continuation<Boolean>): Any? {
    //状态机实现,包含挂起恢复逻辑
    //如果 show() 内部没有挂起点,不会生成状态机,通常直接计算后返回结果。
}

1.4 挂起点

从语法上说是调用一个挂起函数的位置。真正的挂起点是该挂起函数中调用了由协程库提供的原始挂起函数,这些库函数调用了 trySuspend() return了一个枚举值 COROUTINE_SUSPENDED 作为标记,使得当前函数退出执行从而被挂起。

二、协程续体 Continuation

        是对父协程中排在该挂起函数(挂起点)之后的代码的封装(因此这些后续执行的父协程代码就拿到了前一个挂起函数中的结果),可以看做该挂起函数执行完恢复到父协程后要做的事,也就是回调。

        Continuation 是一个接口,子接口 CancellableContinuation 表示可以取消的续体。他们的实现类 ContinuationImpl 和 CancellableContinuationImpl 都是 intel 修饰的,意味着 Continuation 对象不是通过手动创建,而是由编译器生成。

Continuation

1.持有一个属性 context 记录执行代码的环境信息(协程上下文)。

2.抽象方法 resumeWith() 用来恢复到父协程(携带结果)。

BaseContinuationImpl

1.实现了 resumeWith() 并设为 final。

2.定义了 invokeSuspend() 抽象函数由Kotlin编译器自动实现,该方法是状态机的入口。

CancellableContinuation

1.定义抽象方法 cancel() 用来取消协程。

2. invokeOnCancellation() 用来释放资源

3.各种方法判断该协程状态。

ContinuationImpl

增加了intercepted()拦截器功能,实现线程调度等。

CancellableContinuationImpl

trySuspend() 挂起父协程

SuspendLambda

是对 suspend{} 挂起代码块的封装(挂起函数作为函数参数就是suspend lambda 形式)。

2.1 源码 Continuation

是对父协程中排在该挂起函数之后的代码的封装(因此这些后续执行的父协程代码就拿到了该挂起函数中的结果),可以看做该挂起函数执行完恢复到父协程后要做的事,也就是回调。

public interface Continuation<in T> {
    //保存协程的上下文。
    public val context: CoroutineContext
    //携带结果恢复到被挂起的父协程中,结果被封装在Result对象中。
    //可以是:Result .success(传值)、Result .failure(传异常)。
    //为了方便调用可以使用下面两个扩展函数,不然需要手动将值或异常包装为Result对象使用。
    public fun resumeWith(result: Result<T>)
}
//携带值恢复。
public inline fun <T> Continuation<T>.resume(value: T): Unit = resumeWith(Result.success(value))
//携带异常恢复。
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit = resumeWith(Result.failure(exception))

2.2 源码 CancellableContinuation

    public interface CancellableContinuation<in T> : Continuation<T> {
        //检查续体状态
        public val isActive: Boolean
        public val isCancelled: Boolean
        public val isCompleted: Boolean
        //使用可选的异常来结束掉这个续体,成功返回true。
        public fun cancel(cause: Throwable? = null): Boolean
        //当续体被取消或抛出异常时调用,一般用来释放资源。
        public fun invokeOnCancellation(handler: CompletionHandler)
    }

    2.3 源码 CancellableContinuationImpl

    internal open class CancellableContinuationImpl<in T>(
        final override val delegate: Continuation<T>,
        resumeMode: Int
    ) : DispatchedTask<T>(resumeMode), CancellableContinuation<T>, CoroutineStackFrame {
        internal fun getResult(): Any? {
            //父协程成功挂起,就返回COROUTINE_SUSPENDED
            if (trySuspend()) {... return COROUTINE_SUSPENDED }
            //否则就返回异常或值
            if (isReusable) { ... }    //如果父协程已恢复(或自己是伪挂起函数)
            if (state is CompletedExceptionally) { ... }    //如果父协程异常
            if (resumeMode.isCancellableMode) { ... }    //如果父协程已取消
            return getSuccessfulResult(state)    //如果父协程成功计算出值
        }
    }

    三、状态机

    有限状态机 Finite State Machine(FSM)是一种数学模型,描述系统如何响应输入或事件并从一个状态转换到另一个状态。

    组成要素说明
    有限的状态在任何时刻都只能处于一个确定的状态,且其所有可能的状态是有限可穷举的。
    起始状态执行的起点。
    转换规则根据预设的规则,因外部输入或事件而从一个状态转移到另一个。

    3.1 红绿灯例子

    整个过程非常明确,这也是协程状态机的工作模式,只不过它的状态是代码片段,而事件时异步操作的完成。

    • 在任何瞬间,信号灯只会是其中一种颜色。
    • 状态的变迁路径是固定的(红灯永远不会直接跳到黄灯)。
    • 状态转移是由外部事件(计时器结束)驱动的。
    有限状态红灯、黄灯、绿灯。
    起始状态红灯。
    转换规则由定时器触发:红灯亮30秒→切换到绿灯,绿灯亮25秒→切换到黄灯,黄灯亮5秒→切换到红灯。

    3.2 对应协程实现

            在传统的回调地狱中,每一步异步操作都需要一个新的匿名类或 Lambda 来保存上下文。Kotlin 编译器通过将整个挂起函数重写为一个状态机,利用单个对象(Continuation)来保存所有状态。

            结构化并发是状态机的协作框架,通过Job的子父关系,将独立的状态机组织成一个可管理的、生命周期同步的树状结构。

            取消与异常是协作式的,它们依赖于 FSM 在状态转移点(挂起点)的主动检查,并通过 Continuation 和 Job 树进行传播。

    有限状态机组成部分协程对应实现
    有限状态例如具有三个挂起点的协程将恰好有四个状态(起始状态加上每个挂起点之后为一个状态),将已知有限的情况转为简单高效的 swith 语句来跳转到不同分支执行。
    起始状态执行起点,通常称为初始状态或STATE_0。从函数体开始的地方到遇到的第一个挂起函数之间。
    触发事件触发状态转换。可以是:子协程任务完成后的恢复、delay到期。
    转换规则挂起点充当转换。当调用挂起函数时,从当前状态转换到“等待”状态,当操作完成后,又转换到下一个状态。
    动作在转换期间或在状态驻留期间执行的操作,即两个挂起点之间的业务代码。

    3.2.1 没有循环语句

    通过标签(Label)记住当前状态,以便 switch(Label) 跳转到不同状态分支执行代码。第一个挂起函数 delay(1000) 之前的代码就是状态0,父协程执行到 delay(1000) 后被挂起并将 Label 设为1(switch开始执行分支1,即状态1中的代码)。此时来到状态1中,当子协程 delay(1000) 执行完后恢复到父协程中继续执行 println(1),然后遇到第二个挂起函数 delay(2000),父协程又被挂起并将标签设为2。此时来到状态2中,子协程 delay(2000) 执行完后恢复到父协程中继续执行 println(2)......以此类推。

    suspend fun demo() {
        println("0")    //状态0里的动作
        delay(1000)     //第一个挂起点(转换到状态1)
        println("1")    //状态1里的动作
        delay(2000)     //第二个挂起点(转换到状态2)
        println("2")    //状态2里的动作
    }

    3.2.2 存在有限循环

    在有限循环中调用挂起函数,最后一行执行完,循环没结束就是回到循环第一行代码所处的状态中,循环结束了就是进入到下一个状态。delay(1000)执行完进入状态1,delay(2000)执行完进入状态2,println(1.2)执行完,循环没结束就回到状态0执行println(0),循环结束了就继续执行状态1中的 println(1.2)。

    //状态0
    for(i in 0..100) {
        println("0")
        delay(1000)
        //状态1
        println("1.1")
    }
    println(1.2)
    delay(2000)
    //状态2

    3.2.3 存在无限循环

    在无限循环中调用挂起函数,最后一行执行完回到循环第一行代码所处的状态中。delay(1000)执行完进入状态1,delay(2000)执行完进入状态2,println(2)执行完回到状态0执行 println(0)。

    //状态0
    while(true) {
        println("0")
        delay(1000)
        //状态1
        println("1")
        delay(2000)
        //状态2
        println("2")
    }

    3.2 具体实现代码

    将字节码反编译成 Java 代码,具体实现的代码变过,仅作参考,弄懂也没用。

    1. 生成一个 requestWithSuspend() 方法,参数为传递进来的 Continuation 对象。
    2. 第一个 lebal{} 代码块里:
      1. 首先会判断是不是初次运行,是就创建一个对象(挂起函数会创建 ContinuationImpl 对象,挂起函数用作函数参数是Lambda形式会创建 SuspendLambda 对象),将传递进来的的 Continuation 对象用作构造的参数,相当于新的包装了旧的。不是初次运行则不会创建,保证在整个运行期间只会产生一个实例,极大的节省了内存。
      2. ContinuationImpl 对象中的字段(保存一切):
        1. context:字段继承自Continuation,执行代码需要的环境(协程上下文)。
        2. lebal:存储当前的状态用于 switch 语句切换分支执行不同状态的代码(代表下一步从哪个挂起点继续执行)。
        3. result:存储运行的结果(局部变量被提升到状态机的成员变量,这样即便函数挂起(栈帧弹出)数据依然保存在堆上的对象中,与续体绑定这样不会丢失结果)。
        4. invokeSuspend():会重新调用最外层的 requestWithSuspend()。
      3. 重新调用后由于不是初次运行,会跳出该代码块,进入到下一个 lebal{} 代码块,执行 switch() 语句。
    3. 第二个 lebal{} 代码块里,会嵌套众多 lebal{} 代码块,每一个代码块都是一个状态里的业务代码(注意lebal变量只是switch语句的状态),顺序是从里到外。执行完业务代码后就是遇到了下一个挂起点,会先进行存档再判断是否挂起成功,具体是先将 ContinuationImpl 对象的 lebal +1 并保存局部变量,将 ContinuationImpl 对象传递给下一个挂起函数(子协程通过该续体调用 resumeWith() 恢复回来,该方法会调用 invokeSuspend() 进而执行下一个状态的代码,也就是读档),然后判断是否挂起成功,成功就 return 常量 COROUTINE_SUSPENDED 结束父协程函数(挂起父协程,等待子协程调用 resumeWith() 再恢复执行),否则进入外层 lebal{} 代码块执行下一个状态的业务代码。嵌套最里层的那个 lebal{} 代码块里:
      1. 获取 ContinuationImpl 对象中的字段 reslut 存储每次子协程返回的临时结果(就可以给下一个状态用,也就是父协程后续代码可以拿到子协程返回的结果继续执行)。
      2. 有一个变量存着用来返回的挂起常量 COROUTINE_SUSPENDED。
      3. 有一个 switch() 语句根据 ContinuationImpl 对象中的字段 lebal 执行对应分支。只有第一个分支中包含了状态0的业务代码,其它分支里只进行异常判断,因为子协程执行完 resumeWith() 回来会进入到下一个 lebal 对应的分支中。是异常就抛出,不是就跳出当前 lebal{} 代码块执行外层 lebal{} 代码块,即下一个状态中的业务代码。

    Kotlin编译后会为每个协程生成一个 SuspendLambda 的匿名实现类对象(协程嵌套协程会生成多个):

    • 非阻塞式挂起:对于我们在协程函数体中写的代码,会以挂起函数为分割点,将代码分为多个部分(状态)填充到 invokeSuspend() 函数中。当协程开始执行时会首先调用一次 invokeSuspend() 触发初始化,当执行到挂起函数的时候,判断挂起成功会返回COROUTINE_SUSPEND标志,导致 invokeSuspend() 函数 return 停止执行。
    • 状态机:使用 lebal 标记嵌套的代码块方便内部 switch() 跳出,每一层嵌套都是一个挂起函数中的内容(内层是上一个,外层是下一个),每执行完一层就会将 lebal +1,最里层是一个 switch() 语句,当每次恢复调用进来会执行对应状态值的代码(判断Result携带的结果是异常就抛出,正常会跳出对应代码块也就是挂起函数执行完就跳出这层,然后执行内层的上一个挂起函数内容),由此保证了各个状态是按顺序执行(用同步的方式写出异步代码)。
    • Continuation传递:由于实现了Continuation,每次判断挂起成功 return 的时候,SuspendLambda 都会将自己作为续体传递过去。避免了每个挂起函数都需要创建续体对象。
    • 协程的恢复:当挂起函数执行完会调用续体的 resumeWith() 携带结果(值或异常)恢复,而该函数中又会调用 invokeSuspend(),根据状态机的状态值执行下一个状态的代码。
    • 初始调用:挂起函数被调用,编译器生成的状态机函数接收一个 Continuation 参数,label = 0。
    • 执行到挂起点:遇到 delay 等挂起函数,调用其底层实现,挂起协程并返回 COROUTINE_SUSPENDED。
    • 保存状态:label 更新为下一个状态,Continuation 保存上下文。
    • 恢复执行:调度器(如 Dispatchers.Default)在适当时间调用 Continuation.resumeWith,状态机根据 label 跳转到对应分支。
    • 完成或继续挂起:如果没有更多挂起点,返回最终结果;否则继续挂起。

    四、自定义挂起函数

    4.1 用官方挂起函数封装代码

    直接使用系统提供的挂起函数来封装代码是非常方便的,所有官方框架中的挂起函数都是可以取消的。

    • 什么时候定义:需要做耗时操作的时候(I/O、计算、等待)才会挂起当前的协程。
    • 解决了什么:创建者告知这是一个耗时操作,并指定了该协程在后台线程执行,保障了调用者的线程安全。
    • suspend 关键字:用来限制该函数只能在协程里调用或者在其他挂起函数里调用,因为“挂起->执行完->切回去”只有在协程中使用才能实现。真正挂起操作靠的是最终调用的那个协程自带的挂起函数。也有提醒“这是一个耗时操作,是挂起函数要在协程中使用”的意思。
    suspend fun getData(): String = withContext(Dispatchhers.IO) {
        apiService.getData()
    }

    4.2 对回调函数进行改造 suspendCancellableCoroutine

            对于已有的调用了回调的函数可以改造成挂起函数,onSuccess() 回调中通过 resume() 返回数据,onFailure() 回调中通过 resumeWithException() 返回异常,通过 invokeOnCancellation() 在协程被取消时释放资源,必须声明返回值类型(推荐)或对 suspendCancellableCoroutine() 使用泛型。

            不推荐使用不可取消的 suspendCoroutine() 进行包装,需要手动检查协程的取消并抛出 CancellationException 停止后续代码的执行,除非是该函数之后的业务代码关联性非常重要必须执行不能受协程取消的影响。详见:协程的取消

    public suspend inline fun <T> suspendCancellableCoroutine(
        crossinline block: (CancellableContinuation<T>) -> Unit
    ): T =
        //捕获当前协程的续体
        suspendCoroutineUninterceptedOrReturn { uCont ->
            //拦截续体,使之成为可取消的续体
            val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
            //初始化续体
            cancellable.initCancellability()
            //调用我们的业务代码,并提供续体作为参数供使用
            block(cancellable)
            //通过续体获取结果并返回
            cancellable.getResult()
        }
    //改造前
    fun request() {
        getData(object: Callback {
            override fun onSuccess(str: String) { ... }
            override fun onFailure(exception: Throwable) { ... }
        })
    }
    //改造后
    //这里直接声明了返回值类型(推荐),也可以使用泛型 suspendCancelableCoroutine<String>{}
    suspend fun requestWithSuspend(): String = suspendCancelableCoroutine { cancelableContinuation ->
        getData(object: Callback) {
            //携带结果恢复
            override fun onSuccess(str: String) { cancelableContinuation.resume(str) }
            //携带异常恢复
            override fun onFailure(exception: Throwable) { cancelableContinuation.resumeWithException(exception) }
        }
        //取消
        cancellableContinuation.cancel()
        //被取消时的回调,用来释放资源
        cancellableContinuation.invokeOnCancellation { }
        //检查续体状态
        cancellableContinuation.isActive
        cancellableContinuation.isCancelled
        cancellableContinuation.isCompleted
    }

    五、其它系统挂起函数

    delay()

    public suspend fun delay(timeMillis: Long)        // 已不被推荐使用
    public suspend fun delay(duration: Duration): Unit

    延迟一点时间,结束后继续执行当前协程。会检查协程的取消。

    awaitCancellation()

    public suspend fun awaitCancellation(): Nothing

    永久延迟直到被取消,这样不恢复到父协程当前协程就会一直执行。用于那些需要持续执行的服务,注意它之后的代码不会被执行,由于取消会抛出 CancellationException,使用 try-finally 释放资源,没有 catch 快异常会抛给调用者不影响结构化并发。

    measureTime()

    public inline fun measureTime(block: () -> Unit): Duration 

    虽然不是挂起函数,可用于测量作用域内代码执行完的耗时。

    delay(1000)    // IDEA 会提示用下面这个,更类型安全、可读性更好
    delay(1000.milliseconds)

    5.1 awaitCancellation()

    结合 withTimeout() 可在超时前保持执行。

    //协程构建器使用同理
    //没有 catch 块异常会向上抛给调用者,不会影响协程的结构化并发
    suspend fun keepAlive() {
        //初始化服务
        try {
            //启动长时间执行的服务
            awaitCancellation()    //保证协程一直执行直到取消(会抛出CancellationException)
        } finally {
            //清理资源
        }
    }

    六、面试相关

    ①挂起函数不一定挂起协程:

    • 挂起函数不一定真的会挂起,如果只是提供了挂起的条件,但是协程没有产生异步调用,那么协程还是不会被挂起。
    • 当 async() 的返回值 Deferred 已经可以用时,await() 不会挂起协程而是直接返回结果。
    Kotlin协程详解(学习协程这一篇就够了) 一、什么是协程协程是一种优雅处理异步任务的解决方案 ,可以理解为是Kotlin对「线程」和「Handler」 API 的一种封装形式。它可以在不同的线程间来回切换,这样就可以让代码通过编写的顺序来执行,并且不会阻塞当前线程,省去了在各种耗时操作写回调的情形。在 JVM 平台上,Kotlin 协程是无栈协程,所谓无栈协程,是指协程在 suspend 状态时不需要保存调用栈。 阅读详情

    相关推荐

    Kotlin suspend fun 会编译成什么样的代码?

    对于这样的 suspend fun fun foo(p1 : P1, ..., pn : Pn) : R 其编译期生成的代码类似这样: @Nullable public Object foo(P1 p1, ..., Pn pn, @NotNull Continuation continuation) { class LocalContinuationImpl extends kotlin.coroutines.jvm.internal.ContinuationImpl {

    hegan2010的博客 991

    Kotlin 中的suspend 关键字

    1,初认suspendsuspend 用于暂停执行当前协程,并保存所有局部变量,被标记为 suspend 的函数只能运行在协程或者其他 suspend 函数。首先我们看一下在retrofi...

    qxf865618770的博客 1万+

    关于STM32编译报错:Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x_md.o).

    今天新建工程发现出现一个错误 —— …\Output\CG.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x_md.o). 但我觉得我的操作没有任何问题,而且配置和上一个文件比较是一模一样的。 于是我开始百度查询,然后发现了下面几个博客—— @weixin_44133391——STM32 编译错误 @dorlolok——iel5 编译报错 error: L6235E: More than one

    纯粹,学术,热爱。 9403

    Android kotlin实战之协程suspend详解与使用

    Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async与await在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的挂起函数概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。

    4944

    vTaskStartScheduler()详解

    是启动 FreeRTOS 实时内核的“扳机”。在调用它之前,你只是创建了一些任务结构,但它们都处于“待命”状态,系统仍然在按传统的裸机前后台方式运行。调用这个函数后,FreeRTOS 内核会接管系统的控制权,开始根据优先级调度你创建的任务,多任务环境才真正开始。关键点:它没有参数。它的所有行为都由中的配置宏控制。它通常不会返回。一旦调用,除非没有任务可以运行(或者你显式地停止了调度器),否则程序流永远不会回到main()函数中调用它的下一行。是一个没有参数但行为由精密控制的函数。

    Shylock_Mister的博客 1593

    医学超声影像研究——从拒稿到录用:我的UMB投稿实战复盘

    本文分享了作者将一篇关于超声影像中肿瘤传统分类方法的研究论文,从被其他期刊拒稿到最终被《Ultrasound in Medicine & Biology》录用的完整实战经验。文章详细复盘了心态调整、精准选刊、针对UMB期刊的稿件精修、投稿系统操作细节以及与审稿人有效沟通回复的全过程,为医学影像领域的科研工作者提供了宝贵的投稿策略与实操指南。

    stem5的博客 492

    Kotlin协程本质:轻量级挂起计算与结构化并发

    协程是现代 Kotlin 应用中实现高效异步编程的核心机制,它并非线程的简化替代,而是一种基于用户态挂起/恢复的轻量级计算抽象。其底层原理依赖于编译器对 suspend 函数的 Continuation 重写、调度器(Dispatcher)的线程策略抽象,以及作用域(CoroutineScope)驱动的结构化生命周期管理。这种设计显著降低内存开销与上下文切换成本,提升 I/O 密集型任务吞吐能力,并天然支持取消传播与异常处理。在 Android 开发中,它已深度集成于 ViewModel、Lifecycle

    weixin_30225755的博客 288

    测站坐标系统 -- 东北天(ENU)坐标系、站心坐标系

    测站坐标系统以观测站(或地面上某一个观测点为中心建立坐标系统,将这类坐标系称为测站坐标系统。测站坐标系通常以用户所在的位置点P为坐标原点三个坐标轴分别是相互垂直的东向北向和天向,因而测站坐标系又称为东北天(ENU)坐标系。由于测站坐标系以站心为坐标原点,因此也称为站心坐标系。如图所示,测站坐标系的天向与大地坐标系(LLA经纬度坐标系)在此点的高程方向一致。测站坐标系固定在地球上,本质上是一种地固坐标系。(1)以站心为坐标原点。(2)Z轴与站心点的椭球法线重合,向上为正。

    qq_43381998的博客 1万+

    Kotlin 协程总结

    文章目录一、协程是什么1.简介2.协程好在哪3.协程具体怎么用a.添加依赖b.开始使用c.suspend4.小结二、挂起是什么1.「挂起」的本质线程:协程:2.怎么就「挂起」了?3.suspend 的意义?4.怎么自定义 suspend 函数?a.什么时候需要自定义 suspend 函数a.具体该怎么写5.小结三、挂起的非阻塞式是怎么回事1.什么是「非阻塞式挂起」2.为什么要讲非阻塞式挂起3.协程...

    yechaoa 2037

    BF8815A

    了解ADBMS1818芯片的人会发现,这款BF8815B和1818有着很多相似的东西,当你了解其中一款AFE之后,另一款你的上手也会特别快,下面文章是我针对1818梳理的一些重要知识点,此篇文章主要讲解BF8815B的一些差异点。

    m0_61973119的博客 561

    YOLOv改进 | YOLOv11更换主干Backbone之MobileNetV2(轻量化主干网络结构--高效轻量移动模型)

    本文提出将YOLOv11的主干网络替换为轻量化的MobileNetV2,以提升模型在移动端设备的部署效率。MobileNetV2通过倒残差结构和线性瓶颈层,显著减少了计算量和参数量(降至1M~3M),同时保持多尺度特征提取能力。实验表明,改进后的模型在移动设备上可实现20-30 FPS的实时检测,适用于手机APP、无人机和工业监控等场景。核心代码展示了倒残差块的实现,通过深度可分离卷积和通道扩展/压缩实现高效特征提取。

    走向CTO的路上... 1461

    pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复

    今天我们来聊聊Kotlin的协程Coroutine。如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine?如果你已经接触过协程,但对协程的原理存在疑惑,那么在阅读本篇文章之前推荐你先阅读下面的文章,这样能让你更全面更顺畅的理解这篇文章。Kotlin协程实现原理:Suspend&CoroutineContextKotlin协程实现原理:Co...

    weixin_39964899的博客 547

    RepHead一种重参数化检测头用于提升YOLOv8在困难样本上的检测性能

    在目标检测任务中,困难样本通常是指那些由于特殊背景、遮挡、低分辨率或极度小物体等因素,导致模型难以识别的样本。这类样本对传统YOLO系列模型的影响较大,常常造成漏检或误检。重参数化(Reparameterization)是一种深度学习中常用的技术,旨在通过对网络参数的变换,使得模型在训练过程中能够更加灵活地适应不同的数据特征。通过对原始网络结构的参数进行重新表达,能够引导模型在训练过程中学习到更加有效的特征。本文详细介绍了YOLOv8的改进——RepHead检测头,并深入分析了它在解决困难样本检测中的应用。

    博客 1209

    Kotlin 协程总结,最新整理《Android架构师面试题解析大全》

    在了解了 suspend 关键字的来龙去脉之后,我们就可以进入下一个话题了:怎么自定义 suspend 函数。这个「怎么自定义」其实分为两个问题:什么时候需要自定义 suspend 函数?具体该怎么写呢?如果你的某个函数比较耗时,也就是要等的操作,那就把它写成 suspend 函数。这就是原则。耗时操作一般分为两类:I/O 操作和 CPU 计算工作。比如文件的读写、网络交互、图片的模糊处理,都是耗时的,通通可以把它们写进 suspend 函数里。

    2401_84149635的博客 1002

    ROS机器人未知环境自主探索功能包explore_lite总结

    本系列文章主要针对ROS机器人常使用的未知环境自主探索功能包explore_lite展开全源码的详细解析,并进行概括总结。 本系列文章共包含六篇文章,前五篇文章主要介绍explore_lite功能包中 explore.cpp、costmap_tools.h、frontier_search.cpp、costmap_client.cpp等源码实现文件中全部函数的详细介绍,第六篇文章进行概括总结,包含自主探索功能包explore_lite的简介,实现未知环境自主探索功能的原理及流程总结,效果演示,使用功能包exp

    慕羽★的博客 4461

    jre9下载 jdk9

    (Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

    Kotlin 协程 0x01

    协程上下文的主要元素包括了Job,和Dispatcher。协程上下文用于一些技术信息和调度相关的信息。协程作用域表示协程的父子关系。

    750

    精雕软件(JDpaint) 5.5 绿色版

    精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

    Kotlin 协程总结,android工程师要求

    } 方法一通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。 方法二和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和 app 一致,且不能取消(什么是协程的取消后面会讲)。 方法三是比较推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的 context 和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)。

    m0_66264655的博客 1606

    NTscan扫描工具

    ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

    Camera2Demo

    在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

    kotlinx.coroutines 协程基础入门:挂起函数、作用域、构建器与调度器实战指南

    本文以 kotlinx.coroutines 官方文档 [docs/topics/coroutines-basics.md](https://link.gitcode.com/i/6c65a997d91bfb7bf43f48eadf6fa80c) 为主体骨架,系统讲解 Kotlin 协程的四大核心构件:挂起函数(suspending function)、协程作用域(CoroutineScope)与

    gitblog_00692的博客 639

    Kotlin中级——协程

    协程可以暂停执行,而不是阻塞线程。这允许一个协程在等待某些数据到达时挂起,另一个协例程在同一线程上运行,从而确保有效的资源利用率。

    松仔Log的博客 284

    Kotlin Coroutines(协程)

    Kotlin Coroutines(协程) 原文链接:https://blog.dreamtobe.cn/kotlin-coroutines/ 发表2017-08-20 在前面的文章,我们提到了kotlin的基本语法、为什么选用kotlin、根据《Effective Java》Kotlin语法层面的优化、kotlin单元测试编写以及kotlin对包大小影响、kotlin与Java对...

    Android小码家 6347

    kotlin协程_Kotlin协程

    kotlin协程In this tutorial, we’ll be looking into Kotlin Coroutines. Coroutines is a vital concept since asynchronous programming is the way to go over the traditional blocking a thread and polling stuf...

    从零开始的教程世界 693

    Kotlin 协程基本概念:从回调地狱到结构化并发的完整理解(基于 YCBlogs Kotlin 笔记)

    本篇以 YCBlogs 仓库中的 Kotlin 协程研究笔记为主线,系统梳理 Kotlin 协程的基本概念:先用 Java 回调嵌套代码对比出协程"以同步方式写异步代码"的核心价值,再展开协程的四大特点、结构化并发思想、进程/线程/协程三者的层次关系,并用一个可运行的"做饭挂起示例"完整演示 `suspendCancellableCoroutine` 的挂起与恢复机制,读完可以建立起 Kotlin

    gitblog_00144的博客 958

    Kotlin协程入门:从runBlocking掌握异步编程

    协程是现代异步编程的重要技术,通过轻量级线程实现高效并发。其核心原理是挂起与恢复机制,允许代码在不阻塞线程的情况下暂停执行。在Android开发中,协程能有效解决回调地狱问题,提升代码可读性和维护性。通过runBlocking等构建器,开发者可以用同步方式编写异步逻辑,特别适用于网络请求、数据库操作等场景。Kotlin协程与Jetpack组件深度集成,配合Retrofit、Room等库能大幅提升开发效率。掌握协程基础后,可进一步学习Flow、Channel等高级特性,构建更健壮的异步应用。

    weixin_34417183的博客 454

    Kotlin语言的协程

    协程是一种轻量级的线程,它允许你以非阻塞的方式编写异步代码。协程能够在一个线程中暂停和恢复执行,比如在执行长时间运行的任务时,可以在中间暂时挂起,允许其他任务执行,这样就能有效地利用计算资源。Kotlin 协程为异步编程提供了一种强大而优雅的解决方案。它不仅提升了代码的可读性,还减少了传统异步编程中的复杂性。在处理网络请求、数据库操作以及其他耗时任务时,协程都能提供良好的性能和用户体验。通过灵活运用协程的特性,开发者可以更轻松地管理并发任务,实现更高效的应用程序。

    2501_91290826的博客 538

    Kotlin协程runBlocking深度解析:桥接阻塞与挂起的核心机制

    在并发编程与异步任务处理中,阻塞与非阻塞的边界往往决定系统的响应性。Kotlin协程通过挂起函数实现轻量级并发,但将挂起世界接入传统阻塞式调用链时,runBlocking便成为关键的桥接工具。它既能启动协程作用域,又会在当前线程上等待所有协程完成,形成“对外阻塞、对内非阻塞”的独特语义。理解其底层事件循环与调度器协作机制,有助于在main函数调试、单元测试、Java回调中安全使用,同时在Android主线程上避开ANR与界面卡顿。本文结合Flow、LiveData、Dispatchers等协程生态,梳理ru

    weixin_34289454的博客 273

    2024年最新Kotlin - 协程基础及原理(1),2024年最新阿里面试失败征兆

    协程上下文在协程中的作用非常大,有它在相当于协程有了装备卡槽一样。你可以将你想添加的上下文对象合并到参数上,然后在其他地方使用。的数据结构有如下特点:可以通过[]以类似List的方式访问任何一个协程上下文对象,[]内是目标协程上下文。协程上下文可以通过的方式依次累加,当然+=也是可用的。我们来自定义一个协程上下文给协程添加一个名字:应用到示例中:suspend {.name}”)})//输出:c0-01其实协程已经为我们提供了实现。通过实现拦截器接口。

    2401_84561579的博客 915
    上一篇: Kotlin - 线程 Thread
    下一篇: Kotlin 协程 - 协程上下文 CoroutineContext
    Jomurphys
    博客等级 码龄9年 1768粉丝 261原创
    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    个

    红包个数最小为10个

    元

    红包金额最低5元

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

    抵扣说明:

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

    余额充值