Kotlin协程suspend的理解
suspend修饰符,它可以告诉编译器,该函数需要在协程中执行。作为开发者,您可以把挂起函数看作是普通函数,只不过它可能会在某些时刻挂起和恢复而已。协程的挂起就是退出方法,等到一定条件到来会重新执行这个方法,因为对方有这个协程的引用,当然可以调用你的方法了。
普通函数不可以调用挂起函数。因为挂起函数有有一个隐形的参数Continuation
,普通方法没有办法传Continuation
参数,Continuation
可以简单理解成java中的Callback
。
suspend修饰的方法不一定真的可以挂起,只要这个方法接收到COROUTINE_SUSPENDED
结果,就表示可以挂起了,即退出方法。
// 一个普通方法
private fun doFun1() {println("doFun1")
}// 一个suspend修饰的方法,但是方法内没有挂起点,方法体可普通方法一样,但是多一个Continuation参数
private suspend fun doSuspendFun1(p1: Int) {println("doSuspendFun1")
}// 方法中用了delay方法,delay方法返回了COROUTINE_SUSPENDED
// 表示doSuspendFun2方法在执行完delay方法后就会退出
private suspend fun doSuspendFun2(p1: Int, p2: Int) {delay(1000)println("doSuspendFun2")
}fun main() {GlobalScope.launch {println("launch")doSuspendFun2(1, 1)}
}
launch
方法的参数block
就是一个suspend CoroutineScope.() -> Unit
类型的方法。
我们看下转换成java后的代码
doFun1
方法没有什么变化
private static final void doFun1() {System.out.println("doFun1");
}
doSuspendFun1
方法不仅多了一个参数Continuation
,还多了一个返回值Object
类型,这就是为什么普通函数不能调用挂起函数,因为无法传递Continuation
参数。返回值是Object,因为可以返回COROUTINE_SUSPENDED
,也可以返回String,返回值就是协程的返回值,所以返回值的类型无法确定。因为内部没有使用挂起函数,内部的逻辑就和普通方法一样,就是多一个参数和一个返回结果。
private static final Object doSuspendFun1(int p1, Continuation $completion) {System.out.println("doSuspendFun1");return Unit.INSTANCE;
}
来看一下比较复杂的doSuspendFun2
方法,内部使用了delay
方法。就会有挂起点,所谓挂起点就是label
,很多文章叫这个为状态机
。执行到一个挂起点,label
的值就会加1,等下次恢复的时候,switch
就会知道从哪开始执行了。
private static final Object doSuspendFun2(int var0, int var1, Continuation var2) {// Continuation都是从上一个挂起函数中传进来的,并不是当前对象,所以才会重新创建new doSuspendFun2.1(var2)// 防止直接使用外面的的协程var2,影响到var2。doSuspendFun2.1 $continuation;label20: {// if (var2 instanceof doSuspendFun2.1) {$continuation = (doSuspendFun2.1)var2;if (($continuation.label & Integer.MIN_VALUE) != 0) {$continuation.label -= Integer.MIN_VALUE;break label20;}}// 就是下面的 Test14Kt$doSuspendFun2$1$continuation = new doSuspendFun2.1(var2);}Object $result = $continuation.result;// 它的值就是 COROUTINE_SUSPENDEDObject var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();switch ($continuation.label) {case 0:ResultKt.throwOnFailure($result);$continuation.getContext();// label加1$continuation.label = 1;if (DelayKt.delay(1000L, $continuation) == var5) {// delay返回 COROUTINE_SUSPENDED,所以直接退出方法return var5;}break;case 1:ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}System.out.println("doSuspendFun2");// 没有返回值,默认就是 Unitreturn Unit.INSTANCE;}
协程中的Continuation
这样理解成java中的Callback
,应该简单了吧
挂起方法doSuspendFun2
最后被编译成一个对象,继承ContinuationImpl
final class Test14Kt$doSuspendFun2$1 extends ContinuationImpl {// $FF: synthetic fieldObject result;int label;Test14Kt$doSuspendFun2$1(Continuation $completion) {super($completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Test14Kt.access$doSuspendFun2(0, 0, (Continuation)this);}
}
suspend修饰的方法中如果使用了挂起函数,并且挂起函数可以返回COROUTINE_SUSPENDED
,jvm就会创建一个继承自ContinuationImpl
的类,这个类的invokeSuspend
方法就是在执行这个挂起方法(此时已经被转换成java的普通方法了)。
我们再看看main
函数里面的launch
处的方法。
final class Test14Kt$main$1 extends SuspendLambda implements Function2 {int label;Test14Kt$main$1(Continuation $completion) {super(2, $completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();switch (this.label) {case 0:ResultKt.throwOnFailure($result);System.out.println("launch");Continuation var10002 = (Continuation)this;this.label = 1;// 注意把当前对象传给 doSuspendFun2了,这就是Continuation再传递 if (Test14Kt.access$doSuspendFun2(1, 1, var10002) == var2) {// 如果doSuspendFun2方法返回COROUTINE_SUSPENDED,也会退出invokeSuspend方法return var2;}break;case 1:ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}return Unit.INSTANCE;}@NotNullpublic final Continuation create(@Nullable Object value, @NotNull Continuation $completion) {return (Continuation)(new Test14Kt$main$1($completion));}@Nullablepublic final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation p2) {return ((Test14Kt$main$1)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);}
}
suspend CoroutineScope.() -> Unit
会被编译继承SuspendLambda
的一个类。
相关文章:
Kotlin协程suspend的理解
suspend修饰符,它可以告诉编译器,该函数需要在协程中执行。作为开发者,您可以把挂起函数看作是普通函数,只不过它可能会在某些时刻挂起和恢复而已。协程的挂起就是退出方法,等到一定条件到来会重新执行这个方法&#x…...

基于AI深度学习的中医针灸实训室腹针穴位智能辅助定位系统开发
在中医针灸的传统治疗中,穴位取穴的精确度对于治疗效果至关重要。然而,传统的定位方法,如体表标志法、骨度折量法和指寸法,由于观察角度、个体差异(如人体姿态和皮肤纹理)以及环境因素的干扰,往…...

51单片机教程(二)- 创建项目
1 创建项目 创建项目存储文件夹:C51Project 打开Keil5软件,选择 Project -> New uVision Project: 选择项目路径,即刚才创建的文件夹 选择芯片,选择 Microchip(微型集成电路)࿰…...
Rust 图形界面开发——使用 GTK 创建跨平台 GUI
第五章 图形界面开发 第一节 使用 GTK 创建跨平台 GUI GTK(GIMP Toolkit)是一个流行的开源跨平台图形用户界面库,适用于创建桌面应用程序。结合 Rust 的 gtk-rs 库,开发者能够高效地构建现代化 GUI 应用。本节将详细探讨 GTK 的…...

Hellinger Distance(赫林格距离)
Hellinger Distance(赫林格距离)是一种用于衡量两个概率分布相似度的距离度量。它通常用于概率统计、信息论和机器学习中,以评估两个分布之间的相似性。Hellinger距离的值介于0和1之间,其中0表示两个分布完全相同,1表示…...
【系统架构设计师】七、设计模式
7.1 设计模式概述 设计经验在实践者之间日益广泛地利用,描述这些共同问题和解决这些问题的方案就形成了所谓的模式。 7.1.1 设计模式的历史 建筑师Christopher Alexander首先提出了模式概念,他将模式分为了三个部分: 特定的情景ÿ…...

新工具可绕过 Google Chrome 的新 Cookie 加密系统
一位研究人员发布了一款工具,用于绕过 Google 新推出的 App-Bound 加密 cookie 盗窃防御措施并从 Chrome 网络浏览器中提取已保存的凭据。 这款工具名为“Chrome-App-Bound-Encryption-Decryption”,由网络安全研究员亚历山大哈格纳 (Alexander Hagenah…...

模型拆解(三):EGNet、FMFINet、MJRBM
文章目录 一、EGNet1.1编码器:VGG16的扩展网络 二、EMFINet2.1编码器:三分支并行卷积编码器2.2CFFM:级联特征融合模块2.3Edge Module:突出边缘提取模块2.4Bridge Module:桥接器2.5解码器:深度特征融合解码器…...
齐次线性微分方程的解的性质与结构
内容来源 常微分方程(第四版) (王高雄,周之铭,朱思铭,王寿松) 高等教育出版社 齐次线性微分方程定义 d n x d t n a 1 ( t ) d n − 1 x d t n − 1 ⋯ a n − 1 ( t ) d x d t a n ( t ) x 0 \frac{\mathrm{d}^nx}{\mathrm{d}t^n} a_1(t)\frac{\mathrm{d}^{n-1}x}{\math…...
Python-Celery-基础用法总结-安装-配置-启动
文章目录 1.安装 Celery2.配置 Celery3.启动 Worker4.调用任务5.任务装饰器选项6.任务状态7.定期任务8.高级特性9.监控和管理 Celery 是一个基于分布式消息传递的异步任务队列。它专注于实时操作,但也支持调度。Celery 可以与 Django, Flask, Pyramid 等 Web 框架集…...
vue中的nextTick() - 2024最新版前端秋招面试短期突击面试题【100道】
nextTick() - 2024最新版前端秋招面试短期突击面试题【100道】 🔄 在Vue.js中,nextTick 是一个重要的方法,用于在下次DOM更新循环结束之后执行回调函数。理解 nextTick 的原理和用法可以帮助你更好地处理DOM更新和异步操作。以下是关于 next…...
5G学习笔记三之物理层、数据链路层、RRC层协议
5G学习笔记三之物理层、数据链路层、RRC层协议 物理层位于无线接口协议栈的最底层,作用:提供了物理介质中比特流传输所需要的所有功能。 1.3.1 传输信道的类型 物理层为MAC层和更高层提供信息传输的服务,其中,物理层提供的服务…...
Ubuntu 通过Supervisor 或者 systemd 管理 .Net应用
在 Ubuntu 上安装 .NET 8.0,通过 supervisor 或 systemd 管理 .NET 应用服务,确保应用能够自动启动、运行以及在崩溃时重启。 1. 安装 .NET 8.0 最新的Ubuntu版本已经不需要注册 Microsoft 包存储库了,具体的可以参考微软官方文档安装&…...

超好用的视频剪辑软件分享:10款剪辑软件推荐
视频剪辑软件哪个比较好用?无论是短视频创作者、专业剪辑师,还是影视后期制作团队,选择一款合适的视频剪辑软件至关重要。今天,我将为大家分享几款超好用的视频剪辑软件,并介绍视频剪辑的六大核心流程。 1.影忆 特点&a…...
5G三大应用场景中的URLLC
5G三大应用场景中的URLLC 5G三大应用场景中的URLLC 1 Urllc不是一个独立的技术,更不是一张独立的网络,他是5G所谓的新空口标准NR(New Radio)中,涉及大规模降低时延、提高可靠性的相关技术; 2 Urllc在目前的…...

PyMOL中常用的命令列表
PyMOL中常用的命令列表 PyMOL中常用的命令列表,包括了加载文件、去除水分子、改变颜色、显示样式和图形优化等操作,可以帮助你完成全方位的分子展示设置。 基础命令流程 加载分子结构 load your_file.pdb # 加载PDB文件去除水分子 remove solvent …...
坏块处理 ORA-01578: ORACLE data block corrupted (file # 3, block # 152588)
帮客户检查环境时,发现sysaux表空间的数据文件有坏块,8月25日发生的,备份保留3个月,直接恢复处理。 rman备份log报错如下 RMAN-00571: RMAN-00569: ERROR MESSAGE STACK FOLLOWS RMAN-00571: RMAN-03009: failure of backu…...

像`npm i`作为`npm install`的简写一样,使用`pdm i`作为`pdm install`的简写
只需安装插件pdm-plugin-i即可: pdm plugin add pdm-plugin-i 然后就可以愉快地pdm i了,例如: git clone https://github.com/waketzheng/fast-dev-cli cd fast-dev-cli python -m pip install --user pipx pipx install pdm pdm plugin a…...

DNS域名解析服务器--RHCE
1.DNS简介 DNS ( Domain Name System )是互联网上的一项服务,它作为将域名和 IP 地址相互映射的一个分布式 数据库,能够使人更方便的访问互联网 DNS 系统使用的是网络的查询,那么自然需要有监听的 port 。 DNS 使用的是…...
数据库物化视图的工作原理与Java实现
引言 物化视图(Materialized View)是数据库中一种特殊的对象,它存储了查询结果的物理副本,使得复杂查询的结果可以快速地被访问。本文将详细介绍物化视图的工作原理、技术策略,并提供Java代码示例。 1. 物化视图的基…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...