仅仅终止线程是一个糟糕的方案,协程的取消能够更好的检查状态关闭释放资源。
- 运行出错或者调用cancel()后协程会在遇到第一个挂起点开始取消该job并抛出CancellationException异常(先处于Cancelling状态:isActive=false,isCancelled=true),之后会调用join()让协程挂起把该job的取消执行完后(再处于Cancelled状态:isCompleted=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 状态的判断来响应 cancel() 操作。
| isActive 加在判断里 | 是一个CoroutineScope的扩展属性,判断Job是否处于活跃状态。 |
| ensureActive() 写在函数里 | 是一个CoroutineScope的扩展函数,返回coroutineContext扩展函数,调用Job的函数,最终调用的是 !isActive,Job处于非活跃状态就抛异常。 |
| yield() 不至于抢占太多线程让其它协程拿不到执行权 | 会检查所在协程的状态,如果已经取消则报错 CancellationException,此外会尝试让出线程执行权。 |
- suspend fun main() = runBlocking {
- val job = launch(Dispatchers.Default) {
- var num = 0
- while (num < 5 && isActive) { //判断出false便会取消
- ensureActive() //检测出false便会取消
- yield() //不至于因为任务太抢占资源导致其它协程拿不到线程执行权
- println("CPU密集任务")
- num++
- }
- }
- delay(1000)
- println("等完")
- job.cancelAndJoin() //cancel()会将 isActive = false
- println("结束")
- }
由于我们可以捕获CancellationException异常,在 Job 真正结束前可以做一些事情,由于 Job 响应 cancel() 后已经处于 Cancelling状态,此时启动一个新协程(会被忽略)或者调用挂起函数(会抛异常CancellationException)是无法被执行的。
- 方式①:指定协程上下文为NonCancellable来得到一个常驻Job不响应 cancel()操作。
- 方式②:使用invokeOnCompletion()函数,当 Job状态 为 Cancelled 时会执行回调。形参it是一个异常,没有异常值为null,协程被取消值为 CancellationException。
- withContext(NonCancellable){ //不会响应取消 }
- job.invodeOnCompletion{ //回调代码 }
超时取消Job,报错不处理会导致程序结束。
| withTimeout() | public suspend fun 超时取消,抛出异常会结束程序。 |
| withTimeoutOrNull() | public suspend fun 超时取消并返回null,替代抛出异常。 |
- withTimeout(1000) {
- println("")
- }
- withTimeoutOrNull(1000) {
- println("")
- } ?: "值为空"