当前位置: 首页 > news >正文

使用协程简化异步资源获取操作

异步编程的两种场景

在异步编程中,回调函数通常服务于两种不同场景:

  1. 一次性资源获取:等待异步操作完成并返回结果。
  2. 持续事件通知。监听并响应多个状态变更。

Kotlin为这两种场景提供了解决方案:使用挂起函数简化一次性资源获取,使用流处理持续事件通知。关于事件流处理方案详见《将listener转换为事件流》一文。本文聚焦第一种场景:如何简化异步资源获取操作。

异步编程的挑战

异步获取资源接口一般会提供两个状态回调函数:

interface ResourceStateListener {fun onReady(resource: Resource)fun onGone(error: Throwable)
}

有些复杂接口可能提供更多回调函数:

interface ComplexResourceStateListener {fun onOpen(resource: Resource)fun onReady(resource: Resource)fun onError(error: Throwable)fun onConfigureFailure(errorCode: Int)fun onClose()
}

客户代码的核心需求是获取可用资源。根据奥卡姆剃刀“如无必要,勿增实体”的原则,可以将所有资源不可用状态onError/onConfigureFailure/onClose合并成一个:onGone。考虑到onOpen事件只和资源清理有关,不执行业务操作。因此我们真正要关心的只有onReady和onGone。

传统回调的困境

根据上面的例子,我们可以得到代码:

fun openResource(resId: String, listener: ResourceStateListener) { ... }val resId = "1"
openResource(resId, object: ResourceStateListener {override fun onReady(resource: Resource) { ... }override fun onGone(error: Throwable) { ... }
})

传统回调模式存在以下问题:

  1. 代码逻辑分散。资源申请逻辑、使用资源逻辑、错误处理逻辑、资源清理逻辑分割在不同上下文中,代码难以追踪资源状态变化的完整路径。
  2. 状态管理困难。客户代码需要引入额外的变量来在回调函数之间传递资源对象或状态信息,代码复杂度,容易出错。
  3. 容易泄露资源。由于代码分散,难以追踪状态变化的完整路径,很难正确释放资源。容易存在资源泄露或重复释放。
  4. 可读性和可维护性差。回调模式无法满足结构化编程的“单一入口,单一出口”要求,代码难以阅读、理解和修改。

从结构化编程的角度来看,传统回调模式将申请资源代码、使用资源代码和异常处理代码分散在不同的上下文中,无法形成单一入口单一出口的逻辑结构,几乎是现代版的goto变种。

以同步形式编写异步代码

重新观察获取资源的过程:

  1. 程序向系统提交资源申请。系统以异步方式处理申请。
  2. 程序等待系统通知申请结果。
  3. 程序根据结果执行操作。

考虑到Kotlin挂起函数和协程非常适合等待场景,我们可以构造一个挂起函数,发起异步请求后函数挂起,等待回调函数唤醒。对于onReady事件,通过resume唤醒,进入使用资源逻辑。对于onGone事件,通过resumeWithException唤醒,进入错误处理逻辑。同时利用结构化并发特性,在协程退出时清理资源。

suspend fun openResource(resId: String): Resource = suspendCancellableCoroutine { cont ->var internalRes: Resource? = nullval listener = object : ComplexResourceStateListener {override fun onOpen(resource: Resource) {internalRes = resource // 保存底层资源对象引用,必要时手动释放。}override fun onReady(resource: Resource) {if (cont.isActive) {cont.resume(resource) // 资源就绪,唤醒协程。}}override fun onError(error: Throwable) {if (cont.isActive) {cont.resumeWithException(ResourceUnavailableException("Resource error", error))}}override fun onConfigureFailure(errorCode: Int) {if (cont.isActive) {cont.resumeWithException(ResourceUnavailableException("Configuration failed: $errorCode"))}}override fun onClose() {if (cont.isActive) {cont.resumeWithException(ResourceUnavailableException("Resource closed prematurely"))}}}// 发起异步请求。resource.openResource(resId, listener)// 设置资源清理操作。cont.invokeOnCancellation {// 移除监听器,避免后续无效回调干扰和内存泄漏。removeStateListener(listener)// 释放底层资源对象。internalRes?.release()internalRes = null}// 协程在此挂起,等待唤醒。
}// 资源不可用异常
class ResourceUnavailableException(message: String, cause: Throwable? = null) : Exception(message, cause)

封装成挂起函数之后,就可以在协程中以同步的形式编码。

try {val res = openResource(resId)useResource(res)
} catch (e: Exception) {handleError(e)
}

可以看到,使用协程封装回调函数拥有以下优势:

  1. 同步风格代码。使用线性代码结构来表达业务逻辑,代码简洁、意图清晰、可读性良好。
  2. 逻辑完整。资源申请、使用、错误处理逻辑集中在同一个上下文中,理解和维护成本低。
  3. 资源安全。利用协程的invokeOnCancellation和结构化并发特性,​保证资源安全释放,减少资源泄漏风险。
  4. 错误处理简单。所有导致资源不可用的底层错误统一转换成语义清晰的单一异常,简化客户代码错误处理逻辑。
  5. 支持取消。利用协程的取消机制可以取消操作,释放资源。
  6. 接口简单。对客户代码屏蔽了复杂的底层细节,只发布一个简单的挂起函数,提高接口的易用性和语义清晰度。

通过使用协程进行封装,我们将原本支离破碎、难以管理的异步代码转变成结构清晰、资源安全、易于编写和维护的“同步”代码。即提升了开发效率,也大幅增强了程序健壮性。

参考资料

  • 协程指南
  • 将listener转换为事件流
  • 只崩溃软件
  • 我对续体传递风格CPS的理解
  • 结构化并发
  • 结构化并发(2)
http://www.dtcms.com/a/270225.html

相关文章:

  • qt-C++语法笔记之Stretch与Spacer的关系分析
  • Python Web应用开发之Flask框架高级应用(三)——蓝图(Blueprints)
  • openssl 生成国密证书
  • 北京-4年功能测试2年空窗-报培训班学测开-第四十五天
  • [附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+vue实现的供电公司安全生产考试管理系统,推荐!
  • 【OD机试题解法笔记】跳马
  • MySQL8.0.40.0MSI安装教程
  • [特殊字符] AlphaGo:“神之一手”背后的智能革命与人机博弈新纪元
  • 汽车功能安全系统阶段开发【技术安全方案TSC以及安全分析】5
  • TypeScript 接口全解析:从基础到高级应用
  • Crazyflie无人机集群控制笔记(一)通过VRPN实时对接Crazyswarm2与NOKOV度量动捕数据
  • 数据湖技术之Iceberg-03 Iceberg整合Flink 实时写入与增量读取
  • Linux文件描述符与标准I/O终极对比
  • BabelDOC,一个专为学术PDF文档设计的翻译和双语对比工具
  • C#使用Semantic Kernel实现Embedding功能
  • 解决GitHub仓库推送子文件夹后打不开的问题
  • C++高频知识点(六)
  • vue3使用inspira-ui教程【附带源码】
  • Ansible 介绍及安装
  • ubuntu24.04(vmware workstation 17.6pro)无法安装vmtools的问题解决
  • mini-program01の系统认识微信小程序开发
  • 云原生详解:构建现代化应用的未来
  • 【读论文】GLM-4.1V-Thinking 解读:用强化学习解锁 VLM 的通用推理能力
  • Tensor数据转换
  • 模型训练篇 | 如何用YOLOv13训练自己的数据集(以明火烟雾检测举例)
  • 记录一种 Java 自定义快速读的方式,解决牛客中运行超时问题
  • 数与运算-埃氏筛 P1835 素数密度
  • go入门 - day1 - 环境搭建
  • Rust 中字符串类型区别解析
  • 10倍处理效率提升!阿里云大数据AI平台发布智能驾驶数据预处理解决方案