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

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

0x3、第一个官方Demo的解读


如题,官方文档中给出了第一个Kotlin协程Demo:Your first coroutine,笔者加了点料:


image


运行输出结果如下


image


讲解一波(有些名词不懂也没关系,不影响后续学习):


上节说过,Kotlin-JVM的协程是 假协程,只是对底层Thread的一次良好封装,这里通过Thread.currentThread().name 把当前线程的名字打印出来,可以看到协程所用的线程名为:DefaultDispatcher-worker-1,盲猜 线程池,毕竟高效的多线程调度基本是离不开线程池。


GlobalScope.launch 先简单地理解为创建了一个协程,第12行比第8行先执行的原因: 创建线程池要费点时间,所以没主线程同步代码的执行速度快。


delay() 是一个 挂起函数(suspend function),可在不堵塞线程的情况下延迟协程;Thread.sleep() 则会堵塞当前线程;


suspend 挂起的意思:协程作用域被挂起,但当前 线程中协程作用域外的代码不被堵塞suspend挂起函数,只能在协程或者另外一个挂起函数中被调用。


在协程挂起(等待)时,线程会回到线程池,当等待结束,协程会从线程池中一个空闲的线程上恢复。


读者比较疑惑的问题可能是:第13行的那一句 Thread.sleep(2000L) 能去掉吗?


答:不行,这句话的作用是 堵塞主线程,JVM保活,好让协程执行完毕,如果去掉,协程里的东西没执行完,JVM就退出了。


另外再说一点:


main线程只是一个普通的用户线程,其他线程都是由main线程启动的,但在进程层面看:线程都是平级的,没有父子关系,JVM会在所有用户线程执行完毕后退出,注意是 用户线程!JVM可不会理 守护线程 生还是死,可以通过 setDaemon(true) 将线程设置为守护线程。


而打开Kotlin协程源码,全局搜下:isDaemon = true,可见一斑:


image


① runBlocking


另外,Kotlin还提供了一种机制来堵塞线程,可实现与Thread.sleep相同的效果,修改后的代码:


image


背后的机制:


runBlocking函数会建立一个 堵塞当前线程的协程,main线程会等待runBlocking中的代码执行完毕。


上述代码还可以再改进一波:


image


runBlocking是一个全局函数,可在任意地方调用,不过 项目中用得不多,毕竟堵塞main线程意义不大,常用于单元测试防止JVM退出。


还有一点,使用delay()函数可以起到延迟等待作用,但并非良策,实际开发中耗时任务的时间存在不确定性,可以使用Kotlin协程提供的Job(作业)来实现,这个等下会讲。


0x4、CoroutineScope → 协程作用域


① GlobalScope → 全局协程作用域


点进 GlobalScope 的源码:


image


定义成了一个 单例对象, 在整个JVM虚拟中只有一份对象实例,生命周期贯穿整个JVM,故使用时需要警惕 内存泄漏!!!上面讲过Kotlin协程通过作用域来实现「结构化并发」的需求,可以自定义协程作用域以满足我们的需求。


② 自定义作用域


GlobalScope继承自CoroutineScope接口,点开源码,比较简单,持有一个CoroutineContext上下文


image


可以让类实现这个接口,让该类称为一个协程作用域,示例如下:


image


2、使用MainScope()函数


为了在Android/JavaFx等场景中更方便的使用,官方提供了 MainScope() 函数快速创建基于主线程协程作用域。


image


使用MainScope可以很方便的控制所有它范围内的协程的取消,官方更推荐我们定义一个抽象的Activity,示例如下:


image

image


3、使用 coroutineScope()supervisorScope() 创建子作用域


注意,是创建子作用域,只能在一个已有的协程作用域中调用,前者出现异常时会把异常抛出(父协程及其他子协程会被取消),后者出现异常时不会影响其他子协程,示例如下:


image


0x5、创建协程 → 作用域函数


协程作用域,确定了协程间的父子关系,以及取消或异常处理等方面的传播行为。接着,可以利用作用域函数来创建协程。


① launch & async


image


这两个函数会创建一个「不堵塞」当前线程的新协程,区别:


  • launch返回一个「Job」,用于协程监督与取消,用于无返回值的场景。
  • async返回一个Job的子类「Deferred」,可通过await()获取完成时返回值。


简单的代码使用示例如下:


image


输出结果如下


image


0x6、suspend关键字 → 挂起函数


Kotlin协程提供了 suspend 关键字,用于定义一个 挂起函数,它就是一个 标记

当你写的普通函数需要在「某些时刻挂起和恢复」,加上他就行,其他不用你理!!!


而它的真正作用:


告知编译器,这个函数需在协程中执行,编译器会将挂起函数用「有限状态机」转换为一种优化版的回调。


抽取一波业务代码,用suspend定义挂起函数,修改后的代码如下:


image


0x7、Job → 作业


调用launch函数会返回一个Job对象,代表一个 协程的工作任务


① 常用API


/**
 * 协程状态
 */
isActive: Boolean    //是否存活
isCancelled: Boolean //是否取消
isCompleted: Boolean //是否完成
children: Sequence<Job> // 所有子作业
/**
 * 协程控制
 */
cancel()             // 取消协程
join()               // 堵塞当前线程直到协程执行完毕
cancelAndJoin()      // 两者结合,取消并等待协程完成
cancelChildren()     // 取消所有子协程,可传入CancellationException作为取消原因
attachChild(child: ChildJob) // 附加一个子协程到当前协程上


② 生命周期


Job的生命周期包括一系列的状态:


New(新创建)、Active(活跃)、Completing(完成中)、

Completed(已完成)、Cancelling(取消中)、Cancelled(已取消)


image


注意上图中的 await children,当协程处于完成中 或取消中,会等待所有子协程完成后,才进入已完成或已取消状态。


③ 取消操作详解


  • 取消作用域会取消它的所有子协程;


  • 同一作用域中,被取消的子协程不会影响其余兄弟协程;


  • 协程通过抛出一个特殊的异常CancellationException来处理取消操作,cancel函数中默认会创建一个,也可以自己构建新的实例传入,子协程因为CancellationException而被取消,父协程是不需要进行其他额外操作的;


  • 不能在已取消的作用域中再次启动新的协程;


  • 协程的取消是「协作式」的,协程不会在调用cancel()时立即停止,调用后只是进入 取消中 状态,只有工作完成后才会变成 已取消 状态,所以需要我们在代码中定期检查协程是否处于活动状态。比如下述例子:


image


运行输出结果如下


image


并没有在取消后立即停止,需要我们自己手动去判断,比如在while()循环中加入**isActive**


while(i < 5 && isActive)


也可以使用**ensureActive()来检查,该函数会在Job处于不活跃状时立即抛出异常,可以把它写在循环体的第一行。 还可以使用yield()**来检查,它的第一个操作就是检查Job是否完成,已完成会抛出CancellationException来退出协程。


协程取消后会抛出CancellationException,可以使用try/catch代码块对此异常进行捕获,在finally块中完成资源释放等清理工作。


但要注意一个问题:处于取消中状态的协程不能够挂起,finally中的代码如果涉及挂起,后续代码是不会继续执行的,可以通过 withContext + NonCancellable 来创建一个无法取消的任务,以保证清理任务的完成,示例代码如下:


image


运行结果如下


image


④ 异常处理


Kotlin中异常处理的玩法有三种,先是「try-catch直接捕获作用域内异常」,代码示例如下:


image


输出结果如下


image


:无法使用try-catch去捕获launch和async作用域的异常!!!


然后是「全局异常处理」跟Rx里的 RxJavaPlugins.setErrorHandler 捕获全局异常很相似,而全局协程作用域存在嵌套子父级关系,所以异常可能会依次抛出多个,代码示例如下:


image


输出结果如下


image


:只支持launch()传入,async()传入是无效的;全局异常处理并不能阻止协程取消,只是避免因异常而退出程序。


最后是「异常传播」,协程作用域中异常传播默认是 双向 的表现为:


  • 父协程发生异常,所有子协程都会取消;
  • 子协程发生异常,会导致父协程取消,间接导致兄弟协程也取消;


代码示例如下:


image


输出结果如下:


image


有两种方式将传播变为 单向,即子协程发生异常不会影响父协程及兄弟协程。 其中一种方式就是用 SupervisorJob 代替 Job,修改后的代码示例:


image


另一种是使用上面自定义作用域介绍的 supervisorScope,修改后的代码示例如下:


image


相同的运行结果:


image


相关文章
|
14天前
|
存储 Kotlin
正则表达式在Kotlin中的应用:提取图片链接
正则表达式在Kotlin中的应用:提取图片链接
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
46 4
|
1月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
20 1
|
2月前
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
20 1
|
2月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
29 1
|
2月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
76 1
|
2月前
|
自然语言处理 Java 网络架构
解锁跨平台微服务新纪元:Micronaut与Kotlin联袂打造的多语言兼容服务——代码、教程、实战一次打包奉送!
【9月更文挑战第6天】Micronaut是一款轻量级、高性能的Java框架,适用于微服务开发。它支持Java、Groovy和Kotlin等多种语言,提供灵活的多语言开发环境。本文通过创建一个简单的多语言兼容服务,展示如何使用Micronaut及其注解驱动特性实现REST接口,并引入国际化支持。无论是个人项目还是企业应用,Micronaut都能提供高效、一致的开发体验,成为跨平台开发的利器。通过简单的配置和代码编写,即可实现多语言支持,展现其强大的跨平台优势。
51 3
|
2月前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
57 0
|
2月前
|
调度 Python
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
20 0
|
2月前
|
API 数据处理 数据库
掌握 Kotlin Flow 的艺术:让无限数据流处理变得优雅且高效 —— 实战教程揭秘如何在数据洪流中保持代码的健壮与灵活
Kotlin Flow 是一个强大的协程 API,专为处理异步数据流设计。它适合处理网络请求数据、监听数据库变化等场景。本文通过示例代码展示如何使用 Kotlin Flow 管理无限流,如实时数据流。首先定义了一个生成无限整数的流 `infiniteNumbers()`,然后结合多种操作符(如 `buffer`、`onEach`、`scan`、`filter`、`takeWhile` 和 `collectLatest`),实现对无限流的优雅处理,例如计算随机数的平均值并在超过阈值时停止接收新数据。这展示了 Flow 在资源管理和逻辑清晰性方面的优势。
61 0