朱涛 · Kotlin 编程第一课
朱涛
Google 认证的 Kotlin、Android 开发者专家,博客“Kotlin Jetpack 实战”作者
2196 人已学习
立即订阅
登录后,你可以任选4讲全文学习
推荐试读
换一换
02 | 面向对象:理解Kotlin设计者的良苦用心
07 | 高阶函数:为什么说函数是Kotlin的“一等公民”?
09 | 委托:你为何总是被低估?
课程目录
已更新 29 讲/共 37 讲
开篇词 (1讲)
开篇词 | 入门Kotlin有多容易,精通Kotlin就有多难
基础篇 (16讲)
01 | Kotlin基础语法:正式开启学习之旅
02 | 面向对象:理解Kotlin设计者的良苦用心
03 | Kotlin原理:编译器在幕后干了哪些“好事”?
04 | 实战:构建一个Kotlin版本的四则运算计算器
05 | object关键字:你到底有多少种用法?
06 | 扩展:你的能力边界到底在哪里?
07 | 高阶函数:为什么说函数是Kotlin的“一等公民”?
08 | 实战:用Kotlin写一个英语词频统计程序
加餐一 | 初识Kotlin函数式编程
09 | 委托:你为何总是被低估?
10 | 泛型:逆变or协变,傻傻分不清?
11 | 注解与反射:进阶必备技能
12 | 实战:用Kotlin实现一个网络请求框架KtHttp
加餐二 | 什么是“表达式思维”?
加餐三 | 什么是“不变性思维”?
加餐四 | 什么是“空安全思维”?
春节特别放送 (4讲)
春节刷题计划(一)| 当Kotlin遇上LeetCode
春节刷题计划(二)| 一题三解,搞定版本号判断
春节刷题计划(三)| 一题双解,搞定求解方程
春节刷题计划(四)| 一题三解,搞定分式加减法
协程篇 (8讲)
13 | 什么是“协程思维模型”?
14 | 如何启动协程?
15 | 挂起函数:Kotlin协程的核心
16 | Job:协程也有生命周期吗?
17 | Context:万物皆为Context?
18 | 实战:让KtHttp支持挂起函数
期中考试 | 用Kotlin实现图片处理程序
题目解答 | 期中考试版本参考实现
朱涛 · Kotlin 编程第一课
15
15
1.0x
00:00/00:00
登录|注册
开通超级会员可免费学习本课程,还可解锁海量内容免费学特权。

16 | Job:协程也有生命周期吗?

你好,我是朱涛。今天我们来学习 Kotlin 协程的 Job。
Job 其实就是协程的句柄。从某种程度上讲,当我们用 launch 和 async 创建一个协程以后,同时也会创建一个对应的 Job 对象。另外,Job 也是我们理解协程生命周期结构化并发的关键知识点。通过 Job 暴露的 API,我们还可以让不同的协程之间互相配合,从而实现更加复杂的功能。
虽然前面已经解释过,Job 就是协程的句柄,但你可能还是不清楚它到底是什么,因为句柄本身就是一个比较“虚”的概念。所以在这节课中,我们会从使用的角度入手,来看看 Job 到底能干什么。在充分理解了 Job 的用法以后,我们再来结合它的源代码进一步分析,这样对 Job 也会有一个更加清晰的认知。

Job 生命周期

在上节课我们学习 launch、async 的时候,我们知道它们两个返回值类型分别是 Job 和 Deferred。
// 代码段1
public interface Deferred<out T> : Job {
public suspend fun await(): T
}
而如果你去看 Deferred 的源代码,你会发现,它其实也是继承自 Job 的。对应的,它只是多了一个泛型参数 T,还多了一个返回类型为 T 的 await() 方法。所以,不管是 launch 还是 async,它们本质上都会返回一个 Job 对象
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
划线
笔记
复制
02 | 面向对象:理解Kotlin设计者的良苦用心
07 | 高阶函数:为什么说函数是Kotlin的“一等公民”?
09 | 委托:你为何总是被低估?
春节刷题计划(三)| 一题双解,搞定求解方程
春节刷题计划(四)| 一题三解,搞定分式加减法
题目解答 | 期中考试版本参考实现
开通超级会员免费畅看本课程
开通会员
该文章仅可免费阅读部分内容,如需阅读完整文章,请开通超级会员或单独购买本课程。
登录 后留言

精选留言(11)

  • 面无表情的生鱼片
    思考题:
    代码的执行结果是:
    > First coroutine start!
    > First coroutine end!
    > Process end!
    可见 job2 的代码块并没有被执行。

    分析原因:
    分别打印出 job2 在 job2.join() 前后的状态:

    job2 before join: isActive === false
    job2 before join: isCancelled === true
    job2 before join: isCompleted === false
    // job2.join()
    job2 after join: isActive === false
    job2 after join: isCancelled === true
    job2 after join: isCompleted === true

    可见 job2 创建后并没有被激活。

    val job2 = launch(job) {} 这一行代码指示 job2 将运行在 job 的 CoroutineContext 之下, 而之前的代码 job.join() 时 job 已经执行完毕了,根据协程结构化的特性,job2 在创建后不会被激活,并且标记为Cancelled,然后执行 job2 时,发现 job2 未被激活,并且已经被取消,则不会执行 job2 的代码块,但是会将 job2 标记为 Completed

    作者回复: 很棒的分析!

    2022-02-18
    7
  • 魏全运
    思考题结果:
    First coroutine start!
    First coroutine end!
    Process end!
    没有执行job2的原因是,它的launch中传入了job 作为coroutinecontext,而它已经是complete 状态了,所以不会再执行job2的block 而是直接执行了job2的join ,然后结束。

    作者回复: 分析的不错。

    2022-02-18
    2
  • 白乾涛
    思考题针对性不强。
    因为思考题考察的知识点是 CoroutineContext 上下文,而这一部分是下一节课的内容。

    作者回复: 本质还是“生命周期”、“结构化并发”哈,当然,本质还是CoroutineContext,这不刚好引出下节课嘛~

    2022-02-23
    1
  • Gavin
    "First coroutine start!"
    "First coroutine end!"
    "Process end!"
    通过源码可知launch中传入的CoroutineContext会作为parentJob,而job2的parentJob为job,job协程已经处于completed状态,故不执行job2直接跳过

    作者回复: 没错~

    2022-02-23
    1
  • Allen
    val job = launch {
            log("First coroutine start!")
            delay(1000L)
            log("First coroutine end!")
        }

        job.join()
        val job2 = launch(job) {
            log("Second coroutine start!")
            delay(1000L)
            log("Second coroutine end!")
        }
        log("job2: isActivate: ${job2.isActive}, isCompleted: ${job2.isCompleted}, isCancel: ${job2.isCancelled}")
        job2.join()
        log("Process end!")

    在示例代码中,加了一个打印 job2 的状态,发现 job2 已经被取消了,是因为绑定了 job 后,运行时认为其已经被执行过了,所以直接将其取消了?

    作者回复: 嗯,可以这么理解。

    2022-02-20
  • Allen
    执行的结果为:
    First coroutine start!
    First coroutine end!
    Second coroutine start!
    Second coroutine end!
    Process end!

    结果分析:
    1. 第一个 job.join() 被调用后,当前通过 runBlocking 启动的协程会被挂起,等待当前 job 执行完成。
    2. 当第一个 job 执行完成后,会恢复 runBlocking 对应的协程继续执行。
    3. 当执行到 job2.join() 后,runBlocking 对应的协程又被挂起,并等待 job2 的执行。
    4. 同样的道理,当 job2 执行完成后,会恢复 runBlocking 对应协程的继续执行。
    5. 最终打印 “Porcess end!” 后,runBlocking 运行的协程,对应的线程以及进程会相即结束。


    作者回复: 哈哈,看到你另一个答案了。说实话,不实际运行的话,确实容易忽略一些细节。

    2022-02-20
  • Allen
    执行的结果为:
    First coroutine start
    First coroutine end!

    作者回复: 是的

    2022-02-20
  • better
    由浅入深。学习了~

    作者回复: 加油哈~

    2022-02-18
  • 提醒圈圈去看书
    挂起函数,挂起代码的范围是当前协程对咩?比如说在子协程里执行到了挂起函数,则接下来去执行父协程的代码?和线程会有关系吗?

    作者回复: 这其实取决于当前的上下文环境,你可以对比着看后面的CoroutineContext,应该就会明白了。

    2022-02-18
  • 提醒圈圈去看书
    老师,想要请教一下这样的场景:3个异步请求A,B,C。1、B依赖A结果,C依赖B结果,这时是放一个协程里,依次执行三个挂起函数?2.ABC可以同时请求,则一个父协程,分别用async开启三个子协程来执行ABC?3.AB可以同时请求,C依赖AB的结果,则一个父协程,分别用async开启两个子协程执行AB,C为挂起函数放在父协程里面,在await之后再执行对咩。

    作者回复: 如你所说,存在依赖关系的时候,我们就可以挂起函数与async结合了。

    2022-02-18
  • 张国庆
    最后问题应该是按顺序打印

    作者回复: 你可以试着运行一下看看效果,不要忽略了“launch(job) {}”当中的job参数哦!

    2022-02-18
收起评论
11
返回
顶部