kotlin 协程笔记

协程笔记

1、launch 执行 挂起执行,后面的逻辑不会阻塞

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch{
    println("1 CoroutineScope.launch ${Thread.currentThread().name} ...")
    // 挂起执行,后面的逻辑不会阻塞
    launch {
        delay(2000)
        println("2 CoroutineScope.launch ${Thread.currentThread().name} ... ")
    }
    println("3 CoroutineScope.launch  ${Thread.currentThread().name} ...")
}

2025-08-20 22:13:01.863 30511-30511 System.out com.example.kotlinscope I 1 CoroutineScope.launch main ...

2025-08-20 22:13:01.864 30511-30511 System.out com.example.kotlinscope I 3 CoroutineScope.launch main ...

2025-08-20 22:13:03.871 30511-30511 System.out com.example.kotlinscope I 2 CoroutineScope.launch main ...

2、串行切线程,切换到IO线程直到io线程执行返回后切回当前线程后再继续执行当前线程

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch{
    println("1 CoroutineScope.launch ${Thread.currentThread().name} ...")
    // 串行切线程,切换到IO线程直到io线程执行返回后切回当前线程后再继续执行当前线程
    withContext(Dispatchers.IO) {
        delay(2000)
        println("2 CoroutineScope context  ${Thread.currentThread().name} ... ")
    }
    println("3 CoroutineScope.launch  ${Thread.currentThread().name} ...")
}

2025-08-20 22:16:25.385 30728-30728 System.out com.example.kotlinscope I 1 CoroutineScope.launch main ...

2025-08-20 22:16:27.395 30728-30764 System.out com.example.kotlinscope I 2 CoroutineScope context DefaultDispatcher-worker-3 ...

2025-08-20 22:16:27.397 30728-30728 System.out com.example.kotlinscope I 3 CoroutineScope.launch main ...

3、将方法进行抽取 实现串行执行(顺带将withContext 一起抽取)

类似回调里再进行回调方法最终第二个返回进行数据展示 在不同线程间串行

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch{
    val data = getData()
    val processData = processData(data)
    println("process data $processData")
}

private suspend fun processData(data: String) = withContext(Dispatchers.Default) {
    // 处理数据
    "process $data"
}

private suspend fun getData() = withContext(Dispatchers.IO) {
    // 网络代码
    "data"
}

串行方式2

kotlin 复制代码
private suspend fun func1():String {
    delay(2000)
    return "func1"
}

private suspend fun func2():String {
    delay(1000)
    return "func2"
}

// 串行执行,同一个线程串行
CoroutineScope(Dispatchers.Main).launch{
    val f1 = func1()
    val f2 = func2()
    println(f1 + f2)
}

4、概念,挂起函数为什么不卡线程

复制代码
// 使用 launch 的 Dispatchers.Default 其实底层是 维护了一个线程池来把代码放在线程池进行运行
// 使用 launch 的 Dispatchers.Main 能切到主线程是因为底层调用了handler.post 方法

5、协程的异步执行

方式1

kotlin 复制代码
// async 启动的协程会有一个返回对象
val deferred = lifecycleScope.async {
    github.contributors("square","okhttp")
}

// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
    val res1 = github.contributors("square","retrofit")
    val res2 = deferred.await()
    showContributors(res1 + res2)
}

方式2

kotlin 复制代码
val deferred1 = lifecycleScope.async {
    github.contributors("square","okhttp")
}
val deferred2 = lifecycleScope.async {
    github.contributors("square","okhttp")
}

// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
    val res1 = deferred1.await()
    val res2 = deferred2.await()
    showContributors(res1 + res2)
}

方式3:

kotlin 复制代码
// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
    val deferred1 = async { github.contributors("square","okhttp") }
    val deferred2 = async { github.contributors("square","okhttp") }
    showContributors(deferred1.await() + deferred2.await())
}

// 异步执行,最后进行数据合并显示,如下写法更优,结构化并发的附加操作
lifecycleScope.launch {
    coroutineScope {
        val deferred1 = async { github.contributors("square","okhttp") }
        val deferred2 = async { github.contributors("square","okhttp") }
        showContributors(deferred1.await() + deferred2.await())
    }
}

6、结构化并发

取消协程

kotlin 复制代码
val job = lifecycleScope.launch {
    println("start....")
    delay(1000)
    println("end....")
}
job.cancel()

第二行end...不会打印,一般在页面退出的时候取消当前协程。

kotlin 复制代码
override fun onDestroy() {
    super.onDestroy()
    job?.cancel()
}

job取消自己的job

onDestory 中 lifecycleScope 会自动注册cancel 取消所有子孙 启动的协程

launch 外面的管理里面的 launch ,有父子关系

kotlin 复制代码
lifecycleScope.launch { 
    launch { 

    }
}

7、协程join 等待,和async 的区别是,async是有返回值的等待,而join则是没有返回值的等待,使用场景不同

kotlin 复制代码
lifecycleScope.launch {
    val initJob = launch {
        init()
    }
    initJob.join() // 等待初始化完成后后面的才能继续执行,流程上没有返回使用launch join,如果有返回依赖请使用async await 
    processData()
}

8、让回调格式的函数装在挂起函数里

kotlin 复制代码
// 让回调格式的函数装在挂起函数里,使用  it.resume(response.body()!!) 进行返回,返回值val 进行接收
// 也可以对其进行抽取使用
lifecycleScope.launch {
    val contributors: List<Contributor> = suspendCoroutine {
        gitHub.contributorsCall("square", "retrofit")
            .enqueue(object : Callback<List<Contributor>> {
                override fun onResponse(
                    call: Call<List<Contributor>>,
                    response: Response<List<Contributor>>
                ) {
                    it.resume(response.body()!!)
                }

                override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
                    it.resumeWithException(t) //抛出这个异常并结束协程,外部使用try catch 进行捕获
                }
            })
        }
}

9、支持取消的协程 suspendCancellableCoroutine

协程的取消需要协程和内部函数一起配合:例如

kotlin 复制代码
val job = lifecycleScope.launch {
    println("Test cancel 1")
    delay(500)
    println("Test cancel 2")
}

lifecycleScope.launch { 
    delay(200)
    job.cancel()
}

2025-08-20 23:16:23.113 12694-12694 System.out com.example.kotlinscope I Test cancel 1

取消了。

但是如果函数内部不配合,无法取消

kotlin 复制代码
val job = lifecycleScope.launch {
    println("Test cancel 1")
    Thread.sleep(500) // 不配合协程的状态机
    println("Test cancel 2")
}

lifecycleScope.launch {
    delay(200)
    job.cancel()
}

2025-08-20 23:17:20.373 13402-13402 System.out com.example.kotlinscope I Test cancel 1

2025-08-20 23:17:20.873 13402-13402 System.out com.example.kotlinscope I Test cancel 2

基于以上如果协程中存在不配合的代码(java 相关的代码的话)则使用 suspendCancellableCoroutine,外部就可以进行取消了 。

如果使用的 suspendCoroutine 则 job.cancel 无法取消内部的代码执行

kotlin 复制代码
private suspend fun callCancelableSuspend() {
    val contributors: List<Contributor> = suspendCancellableCoroutine {
        it.invokeOnCancellation { 
            // 执行取消之后的收尾工作
        }
        gitHub.contributorsCall(owner = "square", repo = "retrofit")
            .enqueue(object : Callback<List<Contributor>> {
                override fun onResponse(
                    call: Call<List<Contributor>>,
                    response: Response<List<Contributor>>
                ) {
                    it.resume(response.body()!!)
                }

                override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
                    it.resumeWithException(t) //抛出这个异常并结束协程,外部使用try catch 进行捕获
                }
            })
    }
}


job = lifecycleScope.launch {
    try {
        val res = callCancelableSuspend()
        showResult(res)
    } catch (e: Exception) {

    }
}

lifecycleScope.launch {
    delay(200)
    job?.cancel()
}

10、runBlocking介绍

runBlocking 会创建一个新的协程作用域,并且会阻塞当前线程,直到协程作用域内的所有协程都执行完毕 。它允许在一个传统的阻塞式代码环境中(比如在 main 函数中)启动并等待协程执行完成,常用于测试代码、简单的示例代码以及一些非 Android 应用(如命令行工具等)的入口点,方便在不涉及复杂异步回调的情况下体验协程功能。

kotlin 复制代码
// runBlocking 包住 整个main 提供一个协程启动的环境,
// 也可以使用 suspend fun main() = coroutineScope {},但是和 runBlocking 没关系了
// 使用场景 测试一个函数返回
fun main() = runBlocking<Unit> {
    val contributors = gitHub.contributors(owner = "square", repo = "retrofit")
    launch {
        // 此处可添加处理贡献者数据的逻辑
    }
}

// 不需要 coroutineScope 上下文 ,不需要被取消
// 会阻塞线程 ,直到执行完成后才会返回主线程
// 使用:把协程代码转换成 阻塞式的,给线程使用
runBlocking {

}

11、协程异常处理

kotlin 复制代码
val handler = CoroutineExceptionHandler { _, exception ->
    println("捕获到异常: ${exception.message}")
}
lifecycleScope.launch (handler) {
    launch {
        throw RuntimeException("第一个协程的异常")
    }

    launch {
        println("捕获到异常继续执行吗?") // 使用 lifecycleScope.launch 不会执行这里,但是如果用 runBlocking 因为第一个异常结束 launch
        // 这里故意制造一个数组越界异常
        val arr = IntArray(0)
        println(arr[1])
    }
}

2025-08-21 00:07:38.016 25641-25641 System.out com.example.kotlinscope I 捕获到异常: 第一个协程的异常

kotlin 复制代码
val handlerOuter = CoroutineExceptionHandler { _, exception ->
        println("out捕获到异常: ${exception.message}")
}
val handlerInner = CoroutineExceptionHandler { _, exception ->
    println("in捕获到异常: ${exception.message}")
}

lifecycleScope.launch (handlerOuter) {
    launch (handlerInner) {
        throw RuntimeException("第一个协程的异常")
    }

    launch {
        println("捕获到异常继续执行吗????")
        // 这里故意制造一个数组越界异常
        val arr = IntArray(0)
        println(arr[1])
    }
}	

不会执行第二个异常。但如果是runBlocking 会走完整个流程,每一个异常都上报(未验证)

2025-08-21 00:09:05.909 26068-26068 System.out com.example.kotlinscope I out捕获到异常: 第一个协程的异常

相关推荐
肉夹馍不加青椒21 分钟前
第三十三天(信号量)
java·c语言·算法
现在,此刻22 分钟前
面试题储备-MQ篇 2-说说你对RocketMQ的理解
java·rocketmq·java-rocketmq
诗句藏于尽头39 分钟前
更改jar素材后打包
java·jar
SimonKing44 分钟前
开源新锐:SQL玩转搜索引擎?Manticore颠覆你的认知
java·后端·程序员
中国lanwp1 小时前
Jenkins Pipeline中参数化构建
java·jenkins
记录Java学习的三木2 小时前
Java:将视频上传到腾讯云并通过腾讯云点播播放
java
qianmoq2 小时前
第01章:Stream是什么?5分钟让你的循环代码变优雅
java
咖啡里的茶i3 小时前
数字化图书管理系统设计实践(java)
java·课程设计
九转苍翎3 小时前
Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify
java·thread