枯燥的Kotlin协程三部曲(中)——应用实战篇(上)

简介: 上节《枯燥的Kotlin协程三部曲(上)——概念启蒙篇》,追根溯源,先了解并发相关的概念,尔后引出Kotlin协程

0x0、引言


上节《枯燥的Kotlin协程三部曲(上)——概念启蒙篇》,追根溯源,先了解并发相关的概念,尔后引出Kotlin协程:


真正的协程:


  • 一种 非抢占式 / 协作式任务调度模式,程序可 主动挂起或恢复执行


  • 基于线程,相对于线程轻量很多,可理解为 用户层 模拟线程操作;


  • 上下文切换由用户去控制,避免大量中断参与,减少线程上下文切换与调度消耗的资源;


Kotlin中的「假协程」


语言级别并没有实现一种 同步机制(锁),还是依靠Kotlin-JVM提供的Java关键字,锁的实现还是交给线程处理,因此Kotlin协程本质上只是一套「基于原生Java Thread API的封装」。只是这套API 隐藏了异步实现细节,让我们可以用「同步的方法来写异步操作」罢了。


关于Kotlin中的协程的分类有些争议:


没有分配函数调用栈,挂起点状态通过 状态机或闭包等语法实现,是 无栈协程 的无误,


但它又表现出 有栈协程 的特点:可在任意函数调用层及挂起,并转移调用权。


重要概念大概就这些,在学习Kotlin协程的具体API前,容笔者再给大家做些大有裨益的点思想工作。


0x1、思想工作


① 上下文环境


上下文环境 这个词你是怎么理解的?笔者的理解:完成某件事物时所需的前置资源。举个例子:


你周末早上起来,突然想吃韭菜猪肉饺,去市场买了饺子皮、韭菜和五花肉,把韭菜切好,肉绞好,准备开始 包饺子;突然基友夺命call,喊你出去 喝奶茶打王者,那么快乐的事情,怎么能不去。


不过材料都准备好了,晾着等变质或直接丢掉,显然不合理,咋能这样糟蹋粮食! 可以把材料放冰箱,浪完回来再拿出来继续包,从例子中提炼一些东西:


  • 1、包饺子和喝奶茶打王者,是两件 事物


  • 2、做包饺子这件事物的 前置条件 是准备好饺子皮,韭菜和肉馅这些材料(没有材料,包空气?)


  • 3、换句话说包饺子这件事依赖一个 外部的环境(Context),又称 上下文环境


  • 4、此时,基友喊你喝奶茶打王者,你需要停止包饺子这件事物;


  • 5、暂停包饺子这件事物 和 把材料放冰箱里,这叫 挂起(suspend)保存上下文环境


  • 6、浪完回来了继续包饺子 和 把冰箱的材料拿出来,这叫 恢复(resume)恢复上下文环境


注意


这里的 挂起和恢复,是你的 主动行为,要和 堵塞 区分开来,堵塞是 被动行为,比如煮饺子,饺子包好了,但水还没烧开,此时只能 等待


从例子里我们知道了,上下文环境完成某件事务所需的前置资源,我再举一个例子:


你突然有了三个女友,她们也喜欢吃饺子,不过各有所好,大桥未久喜欢玉米、三上悠亚喜欢白菜、桥本有菜喜欢香菜。


所以,在包韭菜馅的饺子时,你会把另外三种馅也包好,此时的事务变成四个:


包韭菜饺子、包玉米饺子、包白菜饺子、包香菜饺子


基友喊你奶茶王者,需要把这四种馅都放冰箱(挂起),问题来了,该怎么放?


直接一把梭往冰箱里怼,肯定是不好的,不好拿是其次,最怕弄乱,毕竟以后你还会有 高橋聖子山岸逢花仲村美羽小野六花松下紗榮子石原希望葵司 这些女友,是吧?一个简单的解决方法:用一个大袋子把对应的资源分别装好,而后贴个 写有女友名字的便利贴 以示区分,就不怕弄乱了~


我们通过写有女友名字的便利贴(不变性) 对 馅料资源 进行标记,以此保证了上下文环境的 唯一性。 对事物进行抽取,包饺子上下文环境如下:


女友名字的便利贴 + 肉馅 + 素馅 + 其他东西


扩展到 线程切换上下文环境,亦是如此:


线程Id + 线程状态 + 堆栈 + 寄存器状态等


扩展到Kotlin协程中的上下文环境 → CoroutineContext,以 键值对 的方式存储各种不同元素:


Job(协程唯一标识) + CoroutineDispatcher(调度器) +

ContinuationInterceptor(拦截器) + CoroutineName(协程名称,一般调试时设置)

妙啊~


② 结构化并发


了解完 上下文 是完成某项事务所需的 外部环境 后,我们再来说说 结构化并发,我们都知道:


多个线程间没有 级联关系,线程执行的上下文是整个进程,并发相对整个进程而非某个父线程。


这是 线程的非结构化,而从业务的角度看:


每个并发操作都是在处理一个任务,它可能属于某个父任务,也可能有自己的子任务。每个任务拥有自己的生命周期,子任务的生命周期理应继承父任务的生命周期。


这是 业务的结构化,Kotlin中的协程就是 结构化的并发


在实际开发中,我们很少需要一个全局的协程,因为它总是跟程序中某个局部作用域相关,这个局部作用域是一个生命周期有限的实体,比如某次网络加载,新建的协程对象和父协程保持着「级联关系」。


具体表现


协程必须在作用域中才能启动,作用域中定义了一些父子协程的规则,Kotlin协程通过作用域来管控域中的所有协程。


网络异常,图片无法展示
|


作用域间可并列包含,组成一个树状结构,这就是Kotlin协程中的结构化并发,说下规则:


先是作用域细分,有下述三种:


  • 顶级作用域:没有父协程的协程所在的作用域;


  • 协同作用域:协程中启动新协程(子协程),此时子协程所在的作用域默认为协同作用域,子协程抛出的未捕获异常都将传递给父协程处理,父协程同时也会被取消;


  • 主从作用域:与协同作用域父子关系一致,区别在于子协程出现未捕获异常时不会向上传递给父协程。


再接着是父子协程间的规则


  • 父协程被取消,所有子协程均被取消;


  • 父协程需等待子协程执行完毕后才会最终进入完成状态,而不管父协程本身的协程体是否已执行完;


  • 子协程会继承父协程上下文中的元素,如果自身有相同Key的成员,则覆盖对应Key,覆盖效果仅在自身范围内有效。


③ 协作式取消


《Kotlin协程官方指南》中说到:取消是协作的,啥意思? 线程的取消和协程的取消在原理上很类似,先从线程的取消说起,再过渡到协程的取消。


如果取消线程


读者的第一反应是不是调用Thread实例的 stop() 或 suspend(),可以是可以,不过不建议这样做。


前者过于粗暴,线程终止前没有对其做任何清除操作,具有固有的不安全性,而后者则具有固有的死锁倾向。


一种更好的方式


在自己的Thread类中置入一个 标志-isAlive(),用于控制目标线程是活动还是停止。


如果该标志指示它要停止运行,可使其结束run()方法; 如果目标线程等待时间过长,则应使用interrupt()方法来中断该等待;


过渡到Kotlin协程,亦是如此,只是:


标志位isAlive() → isActive,中断方法interrput() → cancel()


就是这么简单,具体的取消操作详解,Job那里会讲解一波~


0x2、添加依赖


Kotlin标准库stdlib 中是不包含 Kotlin协程 的,需要 按需另外导入,如:


// 核心库:必要!公共API,使得协程在各个平台的接口得到统一
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8"
// 平台库:当前平台对应的平台库,协程在具体平台的表现方式是有差异的(如Android)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
// 测试库:协程的测试库,方便开发者在测试中使用协程
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.8'


库的版本号必须一致,比如这里同为1.3.8,最新版本号及更多库信息可移步至官方Github仓库自行查阅:github.com/Kotlin/kotl…


:IDEA使用Maven方式导入Kotlin协程依赖:


依次点击:File → Project Structure → Modules → + → Library... → From Maven...


网络异常,图片无法展示
|


输入库的关键词,点击OK,然后等待下载完毕即可。


网络异常,图片无法展示
|


相关文章
|
7月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
10月前
|
缓存 Android开发 iOS开发
Kotlin跨平台Compose Multiplatform实战指南
Kotlin Multiplatform (KMP) 结合 Compose Multiplatform,助力开发者用一套代码构建跨平台应用(Android、iOS、桌面和 Web)。本文提供实战指南,涵盖环境搭建、项目结构、共享 UI 编写、平台适配、状态管理及资源处理等内容。通过 expect/actual 处理差异,借助官方文档与示例项目学习,减少重复代码,优化多平台开发体验。
2466 18
|
Android开发 开发者 Kotlin
Android实战经验之Kotlin中快速实现MVI架构
MVI架构通过单向数据流和不可变状态,提供了一种清晰、可预测的状态管理方式。在Kotlin中实现MVI架构,不仅提高了代码的可维护性和可测试性,还能更好地应对复杂的UI交互和状态管理。通过本文的介绍,希望开发者能够掌握MVI架构的核心思想,并在实际项目中灵活应用。
571 8
|
Java 编译器 测试技术
Kotlin31 协程如何与 Java 进行混编?
Kotlin31 协程如何与 Java 进行混编?
307 2
Kotlin31 协程如何与 Java 进行混编?
|
存储 Kotlin
正则表达式在Kotlin中的应用:提取图片链接
正则表达式在Kotlin中的应用:提取图片链接
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
271 4
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
470 1
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
195 2
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
196 1
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
523 1