Android Kotlin Coroutine(3):Job概述

在 Kotlin 中启动一个协程主要有 2 种方式:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

public fun  CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred

一种是通过 launch 启动,一种是通过 async 启动,前者会返回一个 Job 类型的对象,后者会返回一个 Deferred 类型的对象。

1. Job的接口定义

Job 顾名思义就是“工作”的意思,每个协程可以想象成是一个工作任务,启动一个协程就是启动一个工作任务,来看看 Job 接口的主要定义:

//Job 也是继承自 Element,所以它本身也是一个协程上下文 context
public interface Job : CoroutineContext.Element {
    
    //Key对象,如果你看到 context[Job] 的写法, 就知道其实指的是这里的这个伴生对象 Key
    public companion object Key : CoroutineContext.Key {
        init {
            CoroutineExceptionHandler
        }
    }
    //是否活动状态,必须满足几个条件:该协程已经启动、没有完成、没有被取消
    public val isActive: Boolean
    //是否完成状态
    public val isCompleted: Boolean
    //是否被取消状态
    public val isCancelled: Boolean   
    
    //启动协程,开始调度。如果已经启动了,则返回false。与线程的Thread.start()挺类似
    public fun start(): Boolean
    //挂起当前正在运行的协程,等待该 Job 执行完成。与线程的Thread.join()挺类似
    public suspend fun join()
    //取消该 Job
    public fun cancel(cause: CancellationException? = null)
    //该 Job 的子 Job
    public val children: Sequence
}

2. Job的几个状态

从前面 Job 的接口定义中可以看到,它与线程 Thread 真的很相似,同样都有好几种不同的运行状态,下面通过几个简单的例子直观的感受一下:

2.1 协程没有启动
val job1 = GlobalScope.launch(start = CoroutineStart.LAZY) {
    println("job1 exec...")
}
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")
job1.start()
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

isActive = false, isCompleted = false, isCancelled = false
isActive = true, isCompleted = false, isCancelled = false

懒加载模式,协程还没启动,所以 isActive = false

2.2 协程正常启动运行
val job1 = GlobalScope.launch {
    println("job1 exec...")
}
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

isActive = true, isCompleted = false, isCancelled = false
job1 exec...

协程正常启动,所以isActive = true

2.3 协程被取消
val job1 = GlobalScope.launch {
    println("job1 exec...")
}
//直接取消协程运行
job1.cancel()
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

isActive = false, isCompleted = false, isCancelled = true

协程还没执行完毕,就被取消运行,所以 isCancelled = true

2.4 协程正常完成后取消
val job1 = GlobalScope.launch {
    println("job1 exec...")
}
//暂停一下,让job1能被调度执行完
Thread.sleep(100)
//再次调用取消方法
job1.cancel()
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

job1 exec...
isActive = false, isCompleted = true, isCancelled = false

协程已经执行完毕了,其 isCompleted 肯定为 true 了,再去调用 cancel() 方法,就没有任何影响了,所以 isCancelled = false。一件已经完成的任务,你再去取消它,是没有任何实际意义的了。

2.5 协程没有启动就取消
val job1 = GlobalScope.launch(start = CoroutineStart.LAZY) {
    println("job1 exec...")
}
job1.cancel()
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

isActive = false, isCompleted = true, isCancelled = true

协程还没有启动就取消,发现 isCompleted 与 isCancelled 都为 true,与前面几种情况对比一下,才能真正理解这几种状态的变化。

2.6 由于内部异常导致取消

val job1 = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
    println("job1 exec...")
    //模拟一个异常
    val i = 1 / 0
}
//当前线程暂停,让协程先调度执行
Thread.sleep(100)    
println("isActive = ${job1.isActive}, isCompleted = ${job1.isCompleted}, isCancelled = ${job1.isCancelled}")

执行结果为:

job1 exec...
isActive = false, isCompleted = false, isCancelled = true

协程非正常结束运行,相当于系统把它取消掉了,所以其 isCancelled 也为 true 。

2.6 Job 内部状态的流转

在源码里可以看到以下状态图:

                                      wait children
+-----+ start  +--------+ complete   +-------------+  finish  +-----------+
| New | -----> | Active | ---------> | Completing  | -------> | Completed |
+-----+        +--------+            +-------------+          +-----------+
                 |  cancel / fail       |
                 |     +----------------+
                 |     |
                 V     V
             +------------+                           finish  +-----------+
             | Cancelling | --------------------------------> | Cancelled |
             +------------+                                   +-----------+

3. Deferred接口

首先它也是一个 Job,所以它拥有 Job 的一切特性。其次,它能返回一个结果值,这点与 Java 里的 Future 类特别相似(如果你熟悉的话)。它比 Job 多了一个方法:

public suspend fun await(): T

调用该方法时,它会等待该 Job 执行完并返回一个结果值。这是一个 suspend 方法,只能在协程内部调用,它会暂停协程的执行(当然它并不会阻塞线程),当 Job 执行完返回结果后,它又会恢复协程的执行。

一般在这种情况下,你可能会用到它:

GlobalScope.launch { 
    val job1: Deferred = async { 
        //其他异步执行的代码
        1
    }
    val job2: Deferred = async { 
        //其他异步执行的代码
        2
    }   
    //后面的代码,需要等待1个或多个异步任务执行的结果
    val result = job1.await() + job2.await()
}

4. Job的完成及取消机制

从 Job 的接口定义中可以看到,Job 是可以有很多子 Job 的,如果一个 Job 与其他 Job 没有任何关联,那么它的完成及取消就很简单,不会影响到其他 Job 的执行。如果是有父子关系的 Job,那么他们的完成及取消则是会有相互关联关系的。

4.1 Job必须等待它所有的子Job完成它才能完成
val parentJob = GlobalScope.launch {
    println("parent job start")           //①
    //在内部再启动一个协程,会自动形成父子关系
    val childJob1 = launch {
        println("child job1 start")       //②
    }
    println("childJob1: $childJob1")      //③
    val childJob2 = launch {
        println("child job2 start")       //④
        //延迟1秒钟,方便验证结果
        delay(1000)
        println("child job2 after delay") //⑤
    }
    println("childJob2: $childJob2")      //⑥
    println("parent job end")             //⑦
}
//让 childJob1 能正常完成,childJob2 还在执行中
Thread.sleep(500)
parentJob.children?.forEach {            //⑧
    println("child job name: ${it}")
}
println("isActive = ${parentJob.isActive}, isCompleted = ${parentJob.isCompleted}, isCancelled = ${parentJob.isCancelled}")  //⑨
//让 childJob2 也执行完成
Thread.sleep(600)
println("isActive = ${parentJob.isActive}, isCompleted = ${parentJob.isCompleted}, isCancelled = ${parentJob.isCancelled}")  //⑩

执行结果为:

parent job start     //①
childJob1: StandaloneCoroutine{Active}@37438415      //③
child job1 start     //② 
childJob2: StandaloneCoroutine{Active}@173bd32a      //⑥
parent job end       //⑦
child job2 start     //④
child job name: StandaloneCoroutine{Active}@173bd32a     //⑧
isActive = true, isCompleted = false, isCancelled = false      //⑨
child job2 after delay      //⑤
isActive = false, isCompleted = true, isCancelled = false      //⑩
  • 看一下 ⑧ 处的结果,发现此时 parentJob 只有 1 个子 Job childJob2 了。本来 parentJob 应该有2个子 Job 的,但是当运行在此处时,childJob1 已经执行完毕了,childJob2 由于 delay() 函数的缘故,还处于活动状态中,它们内部应该会自动进行关联及清除操作。
  • 看一下 ⑨ 处的结果,在此时 parentJob 里的代码理论上都执行完了,那它不应该是已完成状态吗?其实不然,此时它还有一个子 Job childJob2 处于活动状态没有完成,父 Job 必须等待其所有子 Job 都完成之后,它的状态才能被标记为完成。
  • 执行 ⑩ 的时候,parentJob 以及其2个子 Job 内部的代码都执行完毕,所以这个时候该 Job 的 isCompleted 为 true 了。
4.2 取消父 Job 会同时取消其所有子 Job
val parentJob = GlobalScope.launch {
    println("parent job start")
    val childJob1 = launch {
        println("child job1 start")
    }
    val childJob2 = launch {
        println("child job2 start")
        delay(1000)
        println("child job2 after delay")
    }
    println("parent job end")
}
parentJob.cancel()

执行结果为:

parent job start
parent job end
child job2 start
child job1 start

parentJob 被取消之后,childJob2 最后也被取消掉了。

4.3 子 Job 如果出现异常会导致父 Job 也被取消掉

你可能感兴趣的:(Android Kotlin Coroutine(3):Job概述)