Android协程(Coroutines)系列-深入理解Job

1,900 阅读3分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

本文同时参与 「掘力星计划」   ,赢取创作大礼包,挑战创作激励金

📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门

Job

val job = lifecycleScope.launch { }

Job 用于处理协程。对于每一个您所创建的协程 (通过 launch 或者 async),它会返回一个 Job 实例,该实例是协程的唯一标识,并且负责管理协程的生命周期。正如我们上面看到的,您可以将 Job 实例传递给CoroutineScope 来控制其生命周期。

一个Job中可以关联多个子Job,同时它也提供了通过外部传入parent的实现

public fun Job(parent: Job? = null): Job = JobImpl(parent)

这个很好理解,当传入parent时,此时的Job将会作为parent的子Job

Job 的生命周期

既然Job是来管理协程的,那么它提供了六种状态来表示协程的运行状态。

  1. New: 创建
  2. Active: 运行
  3. Completing: 已经完成等待自身的子协程
  4. Completed: 完成
  5. Cancelling: 正在进行取消或者失败
  6. Cancelled: 取消或失败

这六种状态Job对外暴露了三种状态,它们随时可以通过Job来获取

public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean

微信图片_20211026174705.jpg

如果协程处于活跃状态,协程运行出错或者调用 job.cancel() 都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)。当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true

val scope = CoroutineScope(Job())

scope.launch() {
    // new coroutine -> can suspend
    launch {
        // Child 1
    }
    launch {
        // Child 2
    }
}

上面已经提及到一个Job可以有多个子Job,所以一个Job的完成都必须等待它内部所有的子Job完成;对应的cancel也是一样的。

默认情况下,如果内部的子Job发生异常,那么它对应的parent Job与它相关连的其它子Job都将取消运行。俗称连锁反应。

我们也可以改变这种默认机制,Kotlin提供了SupervisorJob来改变这种机制。这种情况还是很常见的,例如用协程请求两个接口,但并不想因为其中一个接口失败导致另外的接口也不请求,这时就可以使用SupervisorJob来改变协程的这种默认机制。

SupervisorJob

使用 SupervisorJob 时,一个子协程的运行失败不会影响到其他子协程。SupervisorJob 不会取消它和它自己的子级,也不会传播异常并传递给它的父级,它会让子协程自己处理异常。

所以SupervisorJob只有下面两种使用方式。

  • supervisorScope{}
  • CoroutineScope(SupervisorJob())

这里有个误区,那就是大家不要以为使用SupervisorJob之后,协程就不会崩溃,不管你用什么Job,该崩溃的还是要崩溃的,它们的差别在于是否会影响到别的协程

SupervisorJob使用

fun main() {
    val scope = CoroutineScope(SupervisorJob())

    scope.launch {
        // Child 1
        println("Child 1")
        throw error("Child 1 error ")
    }
    scope.launch {
        // Child 2
        println("Child 2  before")
        delay(1000)
        println("Child 2")
    }
    //这里的sleep只是保持进程存活, 目的是为了等待协程执行完
    Thread.sleep(3000)
}

如果使用CoroutineScope(Job()),Child 1抛出异常后Child 2也将被取消(取消是有条件的,🔺 注意: kotlinx.coroutines包下的所有挂起函数都是可取消的),改用CoroutineScope(SupervisorJob())Child 1抛出异常后Child 2还会继续执行.

supervisorScope{}使用

suspend fun main() {

    supervisorScope {
        launch {
            // Child 1
            println("Child 1")
            throw error("Child 1 error ")
        }
        launch {
            // Child 2
            println("Child 2  before")
            delay(1000)
            println("Child 2")
        }
    }
    //这里的sleep只是保持进程存活, 目的是为了等待协程执行完
    Thread.sleep(3000)
}

这种写法同上面效果一样

❌ 注意:错误写法

方式一 ❌

val scope = CoroutineScope(Job())

scope.launch(SupervisorJob()) {
    // new coroutine -> can suspend
    launch {
        // Child 1
    }
    launch {
        // Child 2
    }
}

方式二 ❌

val scope = CoroutineScope(SupervisorJob())

scope.launch() {
    // new coroutine -> can suspend
    launch {
        // Child 1
    }
    launch {
        // Child 2
    }
}

上面两种写法并不能起到SupervisorJob的作用

因为新的协程被创建时,会生成新的 Job 实例