Kotlin 协程基础入门:Job和协程的生命周期
  pldY3AmarceX 2023年11月28日 16 0



Job简介 在 Kotlin 协程中,一个 Job 对象是一个代表正在执行的异步操作的任务,可以用它来控制与取消协程的执行,是协程执行的核心组件之一。

不同协程创建方式下的返回值

  1. 使用 launch 函数创建协程
//返回 Job
val job = GlobalScope.launch {
    // 协程体
}
  1. 使用 async 函数创建协程
//返回 Deferred<T>
val deferred = GlobalScope.async {
    // 协程体
}
  1. 使用 runBlocking 函数创建协程
//返回 Unit
runBlocking {
    // 协程体
}
  1. 使用 withContext 函数创建协程

用于在协程中切换线程,如在后台线程中执行异步操作,然后返回结果到 UI 线程更新 UI。

//返回被 suspend 修饰的异步函数的返回值
val result = withContext(Dispatchers.IO) {
    // 在 IO 线程中执行异步操作,并返回结果
}

在以上协程创建方式中,Job 对象都被自动管理,开发者无需显式管理,但是可以通过 Job 对象的方法来控制协程的执行,如取消和等待,相当于协程的句柄。

由上面代码可知,通过 launch 函数创建的协程会返回一个 Job 对象。在协程执行完成后,无法获取协程执行的结果。

Job

主要方法:

cancel() 用于取消协程的执行,如果协程已经处于完成状态则无法取消,返回false
join() 等待协程执行完成。该方法是一个挂起函数,会阻塞当前协程,直到被调用的协程执行完成。
invokeOnCompletion() 注册一个回调函数,在协程完成时执行,返回值是一个 DisposableHandle 对象,用于取消这个回调的注册。
onCancel() 注册一个回调函数,在协程被取消时执行。返回值是一个 DisposableHandle 对象,用于取消这个回调的注册。
getChildJob() 创建一个新的子 Job 对象,并将其添加到当前的 Job 上下文中。返回新创建的 Job 对象。
start() 启动协程执行,并返回 true。如果 Job 已经开始执行,则返回 false。

属性:

children 返回当前 Job 的所有子 Job 的序列。
isActive 判断当前 Job 是否处于活动状态,即协程是否正在运行中。
isCancelled 判断当前 Job 是否被取消。
key 获取 Job 在协程上下文中对应的键。
parent 获取当前 Job 的父 Job 对象,如果当前 Job 没有父 Job,则返回 null。
children 返回当前 Job 的所有子 Job 的序列。

生命周期及状态

Job 对象的生命周期可以被描述为以下几个状态:

  • New:协程已创建,但尚未启动。
  • Active:协程正在运行中。
  • Completing:协程已执行完了自己的任务(但可能在等待其子协程完成)。
  • Cancelling:协程正在等待取消操作完成。
  • Cancelled:协程已经被取消,但它的子协程可能仍在运行。
  • Completed:协程已经完成,并不存在需要等待的子协程。

在协程的执行过程中,可以通过 Job 对象上的各种方法来检测协程的状态变化,包括:

  • isActive:检测 Job 是否处于激活状态(即正在运行中),返回 true 或 false。
  • isCompleted:检测 Job 是否已经完成,返回 true 或 false。
  • isCancelled:检测 Job 是否已经被取消,返回 true 或 false。
  • invokeOnCompletion:注册在 Job 完成时执行的回调函数(即协程执行成功或失败时执行的操作)。

Deferred

Deferred 是一种特殊的 Job(继承自 Job 的一个接口),它代表了一段可能产生结果(即返回值)的协程代码。当协程执行成功时,它返回的结果可以通过 Deferred 的 await 方法获取。
await () Deferred 的 await 方法是一个挂起函数,它挂起当前协程的执行,直到所关联的协程执行完成并返回结果或者抛出异常。如果协程抛出了异常,则 await 方法会在当前协程中重新抛出该异常。如果协程成功完成,await 方法会返回对应的值。

例如,如下代码所示,启动一个协程执行异步的计算任务,并通过 Deferred 的 await 方法获取结果:

val deferred = async {
    // 异步计算任务
    // ...
    return@async result // 返回计算结果
}
val result = runBlocking {
    deferred.await() // 等待异步计算任务完成,并返回结果
}

Job的层级关系和结构化并发

具体来说,每个 Job 对象可以关联一个或多个子 Job 对象。这些子 Job 对象可以代表当前协程内部启动的其他协程,或者代表协程启动的某个分支。
当一个父 Job 被取消时,所有的子 Job 都会被自动取消。这种自动取消称为结构化并发,可以避免因子任务的报错或取消而造成的数据错误和资源泄漏等问题。
多说的不说,直接上示例

fun main() = runBlocking<Unit> {
  println("start main coroutine")

  val parentJob = launch {
    println("job start")

    coroutineScope {
      launch {
        delay(200)
        println("job1 finished")
      }

      launch {
        delay(500)
        println("job2 finished")
      }
    } 

    println("job end")
  }

  parentJob.join()

  println("end main coroutine")
}


/// 打印结果
start main coroutine
job start
job1 finished
job2 finished
job end
end main coroutine
///

上述代码中,使用 launch() 启动一个协程任务,并在其中启动了两个子协程任务。这两个子协程会自动成为当前父协程的子协程,当父协程被取消时,这两个子协程也会被自动取消。
可以看到,协程任务执行的顺序是从 job start => job1 finished => job2 finished => job end,这是因为在 coroutineScope 中启动的子协程任务是并发执行的。

调用 parentJob 的 join() 方法,它会等待其内部 job1 和job2 全部执行完毕,才会恢复执行

如果把 parentJob.join() 换成 parentJob.cancel() 那么结果就会变成下面这种情况,其内部的协程被全部取消

/// 打印结果
start main coroutine
end main coroutine
///

所以,当我们以结构化的方式构建协程以后,我们的 join()、cancel() 等操作,也会以结构化的模式来执行。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月28日 0

暂无评论

推荐阅读
pldY3AmarceX