1.Job介绍
基本上每启动一个协程就会产生对应的Job
,例如
lifecycleScope.launch {
}
launch
返回的就是一个Job
,它可以用来管理协程,一个Job
中可以关联多个子Job
,同时它也提供了通过外部传入parent
的实现
public fun Job(parent: Job? = null): Job = JobImpl(parent)
这个很好理解,当传入parent
时,此时的Job
将会作为parent
的子Job
。
2.运行状态
既然Job
是来管理协程的,那么它提供了六种状态来表示协程的运行状态。
-
New
: 创建 -
Active
: 运行 -
Completing
: 已经完成等待自身的子协程 -
Completed
: 完成 -
Cancelling
: 正在进行取消或者失败 -
Cancelled
: 取消或失败
这六种状态Job
对外暴露了三种状态,它们随时可以通过Job
来获取
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
所以如果你需要自己来手动管理协程,可以通过下面的方式来判断当前协程是否在运行。
while (job.isActive) {
// 协程运行中
}
一般来说,协程创建的时候就处在Active
状态,但也有特例。
例如我们通过launch
启动协程的时候可以传递一个start
参数
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
如果这个start
传递的是CoroutineStart.LAZY
,那么它将处于New
状态。可以通过调用start
或者join
来唤起协程进入Active
状态。
下面我们来看一张简图,就能很清晰的了解Job
中的六个状态间的转化过程。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
上面已经提及到一个Job
可以有多个子Job
,所以一个Job
的完成都必须等待它内部所有的子Job
完成;对应的cancel
也是一样的。
3.SupervisorJob
默认情况下,如果内部的子Job
发生异常,那么它对应的parent Job
与它相关连的其它子Job
都将取消运行。俗称连锁反应。
我们也可以改变这种默认机制,Kotlin
提供了SupervisorJob
来改变这种机制。这种情况还是很常见的,例如用协程请求两个接口,但并不想因为其中一个接口失败导致另外的接口也不请求,这时就可以使用SupervisorJob
来改变协程的这种默认机制。
使用很简单,在我们创建CoroutineContext
的时候加入SupervisorJob
即可。例如在上面提到过的lifecycleScope
,内部就使用到了SupervisorJob
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main
)
你也可以尝试运行下面的这个例子,然后将它的SupervisorJob
替换成别的CoroutineContext
再来看下效果。
fun main() = runBlocking {
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// 启动第一个子作业——这个示例将会忽略它的异常(不要在实践中这么做!)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// 启动第二个子作业
val secondChild = launch {
firstChild.join()
// 取消了第一个子作业且没有传播给第二个子作业
println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// 但是取消了监督的传播
println("The second child is cancelled because the supervisor was cancelled")
}
}
// 等待直到第一个子作业失败且执行完成
firstChild.join()
println("Cancelling the supervisor")
supervisor.cancel()
secondChild.join()
}
}
如果有些任务你并不想被手动取消,可以使用NonCancellable
作为任务的CoroutineContext
如果需要Job
获取协程的返回结果,可以通过Deferred
来实现,它是Job
的一个子类,所以也拥有Job
所用功能。同时额外提供await
方法来等待协程结果的返回。
Deferred
可以通过CoroutineScope.async
创建。
最后我们再来介绍下Job
的几个方法,start
与cancel
就不多说了,分别是启动与取消。
4.invokeOnCompletion
这个方法是Job
的回调通知,当Job
执行完后会调用这个方法
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
public typealias CompletionHandler = (cause: Throwable?) -> Unit
这个cause
有三种情况分别为:
-
is null
: 协程正常执行完毕 -
is CancellationException
: 协程正常取消,并非异常导致的取消 -
Otherwise
: 协程发生异常
同时它的返回值DisposableHandle
可以用来取消回调的监听。
5.join
public suspend fun join()
注意这是一个suspend
函数,所以它只能在suspend
或者coroutine
中进行调用。
它的作用是暂停当前运行的协程任务,立刻执行自身Job
的协程任务,直到自身执行完毕之后才恢复之前的协程任务继续执行。
推荐文章