class ExampleClass {...fun exampleMethod() {// Handle to the coroutine, you can control its lifecycleval job = scope.launch {// New coroutine}if (...) {// Cancel the coroutine started above, this doesn't affect the scope// this coroutine was launched injob.cancel()}}
}
我们通过Job可以获取当前协程的运行状态,还可以随时取消协程。
协程的状态查询:val job = GlobalScope.launch(start = CoroutineStart.LAZY) {println("执行在协程中...") delay(1000L) println("执行完毕...")
}
job.start()
var job = GlobalScope.launch { println("执行在协程中...") delay(1000L)println("执行完毕...")
}
...
override fun onDestroy() { job.cancel()super.onDestroy()
}
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> YYLogUtils.e(throwable.message ?: "Unkown Error")
}
val job = GlobalScope.launch(Dispatchers.Main + exceptionHandler) { println("执行在另一个协程中...") delay(1000L) val num = 9/0 println("另一个协程执行完毕...")
}
job.invokeOnCompletion { println("完成或异常的回调")
}
没有异常的回调: 加入9/0的异常代码之后的回调:
fun main() = runBlocking {val job = launch {delay(100)println("hello")delay(300)println("world")}println("test1")job.join()println("test2")
}
输出: suspend fun main() { runBlocking {val job = launch {repeat(1000) { i ->println("job: test $i ...")delay(500L)}}delay(1300L) // 延迟一段时间println("main: job.cancel() called!")// job.cancel() // 取消该作业// job.join() // 等待作业执行结束job.cancelAndJoin() // 等价以上两行,取消一个作业并等待它结束println("main: job is canceled.")}
}
输出: suspend fun main() {runBlocking {val job = launch(Dispatchers.Default) {var nextPrintTime = System.currentTimeMillis()var i = 0while (i < 100) { // 一个执行计算的循环,只是为了占用 CPU// 每秒打印消息两次if (System.currentTimeMillis() >= nextPrintTime) {println("job: hello ${i++} ...")nextPrintTime += 500L}}}delay(1300L) // 等待一段时间println("main: job.cancel() called!")job.cancelAndJoin() // 取消一个作业并且等待它结束println("main: Now job is canceled.")}
}
输出:
可以看到上面代码中当调用 job.cancelAndJoin() 之后,协程并没有被立即取消掉,而是继续执行完while循环所有的计算工作后才自动停止。也就是说上面代码跟先调用 job.join() 后调用 job.cancel() 的效果是一样的。
while (i < 100 && isActive)
while (i < 100 && coroutineContext[Job]?.isActive == true)
while (i < 100 && coroutineContext[Job]?.isCancelled == false)
以上三种写法都可以。
另一种方法使用协程标准库中的函数 ensureActive() :while (i < 100) {ensureActive()...
}
ensureActive() 的实现是这样的:
public fun Job.ensureActive(): Unit {if (!isActive) throw getCancellationException()
}
其实跟第一种原理是一样的,也是调用了isActive来判断的。
第三种办法是使用 yield() 支持可响应取消 :while (i < 100) {yield()...
}
yield()方法是一个可取消的挂起函数,它的作用是:如果当前协程的Job在调用此挂起函数时被取消或完成,或者在此函数等待分派时,它将使用CancellationException作为结果来恢复协程。因此它可保证立即取消协程。 yield() 应该在定时检查中最先被调用。
检查协程是否为已取消状态,在业务开发中是非常有必要的,例如,如果您要从磁盘读取多个文件,请先检查协程是否已取消,然后再开始读取每个文件。someScope.launch {for(file in files) {ensureActive() // Check for cancellationreadFile(file)}
}
fun main() = runBlocking {val deferred = async {...delay(5000)10}val result = deferred.await()
}
deferred 也是可以取消的,对于已经取消的 deferred 调用 await() 方法,会抛出 JobCancellationException 异常。 同理,在 deferred.await() 之后调用 deferred.cancel(), 那么什么都不会发生,因为任务已经结束了。 async 可以通过将 start 参数设置为 CoroutineStart.LAZY 变成惰性的。在这个模式下,调用 await 获取结果时,最好调用 Job 的 start 方法启动协程。 fun main() { runBlocking {val time = measureTimeMillis {val deferred1 = async(Dispatchers.IO, CoroutineStart.LAZY) {printInfo()delay(1000) // 模拟耗时操作1}val deferred2 = async(Dispatchers.IO, CoroutineStart.LAZY) {printInfo()delay(3000) // 模拟耗时操作2}deferred1.start()deferred2.start()printInfo("${deferred1.await() + deferred2.await()}")printInfo("end")}printInfo("time: $time")}
}fun printInfo(msg : String? = null) {println("${Thread.currentThread().name}: ------> ${msg ?: ""}")
}
输出:
问题:如果不调用 start() 情况会怎样?
上面代码如果不调用 deferred1.start() 和 deferred2.start() 方法,输出如下: