Kotlin 协程高级用法之 NonCancellable

核心前提:
NonCancellable = 临时屏蔽取消信号 ,让代码块原子执行不中断可用于任意代码位置 ,没有强制必须写在 finally 里。


用法1:官方推荐用法 → finally 块中(资源清理/收尾)

适用场景

协程无论成功/失败/取消 ,都必须执行的资源释放操作 (关流、关连接、释放锁);

因为 finally 里的挂起函数 会被取消中断,所以必须用 NonCancellable 包裹。

Android 实战代码

kotlin 复制代码
import kotlinx.coroutines.*
import androidx.lifecycle.lifecycleScope

// Android 页面中(Activity/Fragment)
lifecycleScope.launch(Dispatchers.IO) {
    var networkStream: NetworkStream? = null
    try {
        // ========== 可取消区域 ==========
        // 耗时网络/文件操作,cancel() 会立即中断
        networkStream = openNetworkStream()
        readStreamData(networkStream)
        delay(1000) // 挂起点,可被取消
    } finally {
        // ========== 必执行区域:协程任何结束方式都会进入 ==========
        // 官方规范:finally 中执行挂起收尾,必须用 NonCancellable
        withContext(NonCancellable) {
            // 纯资源清理,无业务成功逻辑
            networkStream?.close() // 挂起函数,不被取消中断
            releaseWorkerLock()
            log("资源已安全释放")
        }
    }
}

用法2:项目实战用法 → 业务成功分支中(原子提交)

适用场景

业务已确认成功 (如拿到服务端 resourceId),后续的内存赋值/DB 持久化 必须完整执行,不能被取消中断;
仅成功路径执行,失败/取消不进入。

Android 实战代码

kotlin 复制代码
import kotlinx.coroutines.*
import androidx.lifecycle.lifecycleScope

// Android 页面中(Activity/Fragment)
lifecycleScope.launch(Dispatchers.IO) {
    // ========== 可取消区域 ==========
    // 耗时上传操作,cancel() 会立即断开 OkHttp
    val resourceId = uploadFileToServer()

    // ========== 仅业务成功进入:原子提交区 ==========
    if (resourceId != INVALID_ID) {
        // 屏蔽取消,保证落盘逻辑完整执行
        withContext(NonCancellable) {
            // 1. 内存状态赋值
            currentItem.remoteId = resourceId
            currentItem.status = UploadStatus.SUCCESS
            // 2. 本地持久化(DB/SP)
            saveUploadResultToDb(currentItem)
            // 3. 通知 UI 更新
            withContext(Dispatchers.Main) {
                updateUploadUi(currentItem)
            }
        }
    }
}

用法3:最佳实践 → 两种用法共存(Android 标准工程代码)

成功分支做原子提交,finally 做资源清理,互不冲突、各司其职。

kotlin 复制代码
lifecycleScope.launch(Dispatchers.IO) {
    var uploadConnection: UploadConnection? = null
    try {
        // 1. 可取消:建立连接 + 上传文件
        uploadConnection = createUploadConnection()
        val resourceId = uploadConnection.executeUpload()

        // 2. 用法2:业务成功 → 原子提交(你的项目核心逻辑)
        if (resourceId != INVALID_ID) {
            withContext(NonCancellable) {
                currentItem.remoteId = resourceId
                saveUploadResultToDb(currentItem)
            }
        }
    } finally {
        // 3. 用法1:官方规范 → 无论如何都释放资源
        withContext(NonCancellable) {
            uploadConnection?.close() // 安全关闭连接
        }
    }
}

配套:外部取消的标准写法(Android 环境)

kotlin 复制代码
// 取消上传任务(删除逻辑)
private fun cancelUploadJob(job: Job) {
    lifecycleScope.launch {
        job.cancel() // 立即中断上传(断开 OkHttp)
        job.join()   // 等待:原子提交块 + finally 清理 全部执行完毕
        // 执行后续删除逻辑
        deleteRemoteResource(currentItem.remoteId)
    }
}

总结

  1. 无唯一写法NonCancellable 不是必须写在 finally 中,官方从未规定唯一用法
  2. 两种标准场景
    • finally + NonCancellable官方规范 ,用于协程全生命周期的资源清理
    • 成功分支 + NonCancellable工程实战 ,用于业务结果的原子提交 (你的项目用法)
      取消后必须用 join() 等待不可取消代码执行完毕
相关推荐
天空之城--7 分钟前
Android Flutter行业动态与学习参考(2026年8月)
android·flutter
码匠许师傅23 分钟前
【C++ 面试真题】聊聊 volatile 和 mutable
开发语言·c++·面试
(Charon)31 分钟前
【C++】线程安全队列(二):生产者消费者模型与 condition_variable 阻塞等待
c语言·开发语言·c++
liwulin050642 分钟前
【PYTHON】使用Selenium + ChromeDriver以及XPATH语法
开发语言·python·selenium
JavaPub-rodert1 小时前
我又把自己的 Go 后台管理系统升级了一遍:文件管理、2GB 上传、私有文件预览、Docker 镜像全安排上了
开发语言·docker·golang·shiyuadmin
动力 continue1 小时前
Python 元类与异常类:类的“制造工厂”与“报错定制师”
开发语言·python·制造
quantdash_cc1 小时前
基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
android·开发语言·pandas·量化·quantdash
147API1 小时前
Python批量生成蒸馏数据,并发、重试、幂等和断点续跑
开发语言·jvm·python
青 春 记 忆1 小时前
零基础入门python04:用注册校验理解字典、集合和条件判断
开发语言·vscode·python·python3.11
kels88991 小时前
实战排坑:黄金实时API开发,XAUUSD Tick报文异常处理实践
开发语言·python·websocket·网络协议·信息可视化