任务取消的状态
仅仅终止线程是一个糟糕的方案,协程的取消能够更好的检查状态关闭释放资源。
- 运行出错或者调用cancel()后该job会在遇到第一个挂起点开始取消并抛出CancellationException异常(先处于Cancelling状态:isActive=false,isCancelled=true)(没有挂起点便不会响应cancel()操作),之后会调用join()让协程挂起把该job的取消执行完后(再处于Cancelled状态:isCompleted=true,isCancelled=true)才能继续执行其它,否则会存在其它协程并发执行。推荐使用cancelAndJoin()简化调用。
- 一旦该job被取消,该job下的子job也会一并取消,但父job和兄弟job不受影响,该job不能再用作任何新job的父job(不能开启新协程)。
Job的状态/函数判断 | isActive( ) | isCompleted( ) | isCancelled( ) | New 新创建(optional initial state) | false | false | false | Active 活跃(default initial state) | true | false | false | Completing 完成中(transient state) | true | false | false | Cancelling 取消中(transient state) | false | false | true | Cancelled 已取消(final state) | false | true | true | Compeleted 已完成(final state) | false | true | false |
任务取消的异常处理
协程通过抛出一个 CancellationException异常 来取消 Job。cancel() 可以传参使用不同的异常来指定原因,需要是 CancellationException 的子类才能取消协程。该异常不会导致父协程或其它子协程的取消,可以使用 try-catch-finally 去捕获处理释放资源,推荐使用标准函数?use() 会自动关闭资源。
suspend fun main() = runBlocking {
//没有继承父协程的上下文,有自己的作用域,因此 runBlocking 不会等待 GlobalScope 执行完再结束。
val job = GlobalScope.launch {
try {
//耗时操作
}catch (e:Exception){
//处理异常
}finally{
//释放资源
}
}
delay(1000) //让job运行一下再取消
// job.cancel() //抛异常 JobCancellationException
// job.join() //挂起函数,这样就会等 GlobalScope 取消完再继续执行
job.cancelAndJoin() //简写
}
无法直接取消的任务(CPU密集型、没有挂起点)
由于调用cancel()操作后Job会处于Cancelling状态,此时只需判断Job是否处于活跃状态于便可以响应cancel()操作。
- CPU密集型任务无法直接被cancel()取消,因为直接取消会丢失临时计算数据。可以通过对Job状态的判断来响应cancel()操作。
- Job的取消发生在挂起点上,没有挂起点便不会响应cancel()操作,当我们使用协程却没有调用任何挂起函数的时候(做阻塞操作、神经网络学习)便会发生这种情况。
isActive 加在判断里 | 是一个CoroutineScope的扩展属性,判断Job是否处于活跃状态。 | ensureActive() 写在函数里 | 是一个CoroutineScope的扩展函数,返回coroutineContext扩展函数,调用Job的函数,最终调用的是 !isActive,Job处于非活跃状态就报错CancelllationException。 | yield() 不至于抢占太多线程让其它协程拿不到执行权 | 会检查所在协程的状态,如果已经取消则报错 CancellationException,此外会尝试让出线程执行权。 |
suspend fun main() = runBlocking {
val job = launch(Dispatchers.Default) { //该协程中无挂起点
while (isActive) { //判断出false便会取消
ensureActive() //检测出false便会取消
yield() //不至于因为任务太抢占资源导致其它协程拿不到线程执行权
println("CPU密集任务")
}
}
delay(1000) //让job运行一会儿后再取消
println("等完")
job.cancelAndJoin() //cancel()操作会将 isActive = false
println("结束")
}
一定无法取消的任务
由于我们可以捕获CancellationException异常,在 Job 真正结束前可以做一些事情,由于 Job 响应 cancel() 后已经处于 Cancelling状态,此时启动一个新协程(会被忽略)或者调用挂起函数(会抛异常CancellationException)是无法被执行的。
- 方式①:指定协程上下文为NonCancellable来得到一个常驻Job不响应 cancel()操作。
- 方式②:使用invokeOnCompletion()函数,当 Job?处于Cancelled状态或Compeleted状态时会执行回调。形参it是一个异常,没有异常值为null,协程被取消值为 CancellationException。
withContext(NonCancellable){
//不会响应取消
}
job.invodeOnCompletion{
//回调代码
}
CancellableContinuation<T>
suspendCancellableCoroutine
自定义协程取消时所作的操作。
//定义
suspend fun getResource():StudentBean = suspendCancellableCoroutine{ continuation ->
request(object : ICallBack{
override fun onSuccess(data:String){
continuation.resume(data)
}
override fun onFailure(exception:Throwable){
continuation.resumeWithException(exception)
}
})
//定义协程取消时应该做的操作
continuation.invokeOnCancellation{ //TODO... }
}
//使用
suspend main() = runBlocking{
try{
viewModelScope.launch{
val bean = getResource()
}
}catch(e : Exception){
e.printStackTrace()
}
}
超时取消的任务?
超时取消Job,报错不处理会导致程序结束。
withTimeout() | public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T 超时取消,抛出异常会结束程序。 | withTimeoutOrNull() | public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T?? 超时取消并返回null,替代抛出异常。 |
withTimeout(1000) {
println("")
}
withTimeoutOrNull(1000) {
println("")
} ?: "值为空"
|