Kotlin 协程的取消
1. 取消协程
在 Kotlin 协程中,取消协程是一种常见的操作,尤其在你不再需要协程继续工作时,及时取消它可以有效避免浪费内存和电池寿命。我们可以通过两种方式取消协程:
- 取消整个 CoroutineScope:取消整个
CoroutineScope会同时取消该作用域内的所有协程。 - 取消单个协程:通过调用
job.cancel()取消单个协程,而不影响其他兄弟协程。
示例代码:
val job1 = scope.launch { ... }
val job2 = scope.launch { ... }
scope.cancel() // 取消整个 scope,job1 和 job2 都会被取消
如果只想取消单个协程:
job1.cancel() // 只取消 job1,job2 不受影响
协程取消是通过抛出一个特殊的异常 CancellationException 来实现的。如果你想在取消时提供更多的上下文信息,可以在 cancel 方法中传递自定义的 CancellationException 实例。
2. 为什么我的协程没有停止?
很多开发者可能遇到过协程没有按照预期停止的问题。首先,需要明确一点,调用 cancel() 不意味着协程立刻停止。如果在协程代码中没有适当的取消检查,协程会继续执行直到完成其工作。
示例:
fun testCancelEarly() {
val startTime = System.currentTimeMillis()
scope.launch {
val job = scope.launch {
var nextPrintTime = startTime
var i = 0
while (i < 5) {
if (System.currentTimeMillis() >= nextPrintTime) {
log("Hello ${i++}")
nextPrintTime += 500L
}
}
}
delay(1000L)
log("Cancel")
job.cancel()
log("Done")
}
}
输出结果:
Hello 0
Hello 1
Hello 2
Cancel
Done
Hello 3
Hello 4
尽管调用了 cancel(),协程并没有立即停止,而是继续执行直到所有工作完成。这表明,仅调用 cancel() 不会自动停止协程,开发者需要定期检查协程的活动状态。
3. 让你的协程可取消
要确保你的协程可以被取消,你需要定期检查协程的状态。特别是在执行长期任务(例如从磁盘读取文件时),你需要在每个任务开始之前检查协程是否被取消,以避免不必要的工作。
示例:
val job = launch {
for (file in files) {
if (!isActive) return@launch // 检查协程是否被取消
readFile(file)
}
}
关键点:
所有 kotlinx.coroutines 中的 suspend 函数(例如 withContext、delay)都是可取消的。如果使用这些函数,你无需显式地检查协程状态。否则,使用 isActive 或 ensureActive() 来检查协程是否处于活动状态。
4. 检查 Job 的活动状态
要让协程响应取消操作,你可以使用 isActive 或 ensureActive() 来定期检查协程的状态。
示例代码:
fun cancellableWithContext(view: View) {
val startTime = System.currentTimeMillis()
lifecycleScope.launch {
withContext(Dispatchers.IO) {
var nextPrintTime = startTime
var i = 0
while (i < 15) {
ensureActive() // 检查是否被取消
if (System.currentTimeMillis() >= nextPrintTime) {
log("Hello ${i++}")
nextPrintTime += 1000L
}
if (i == 5) {
finish()
}
}
}
log("Done")
}
}
这里,ensureActive() 会抛出一个 CancellationException,如果协程已经被取消,确保取消操作被正确处理。
5. Job.join() 和 Deferred.await() 的取消
Job.join() 和 Deferred.await() 是协程中等待另一个协程执行完成的两种方式。在使用它们时要注意,协程的取消可能会影响这两个方法的行为:
- 如果你在调用
join()或await()之前取消了Job,则isActive会被设置为false,协程不会继续执行。 - 在
join()或await()之后调用cancel()不会影响已经完成的协程。
示例:
fun testJobCancel(view: View) {
val startTime = System.currentTimeMillis()
lifecycleScope.launch {
val job = launch {
var i = 0
var nextPrintTime = startTime
while (i < 5) {
if (System.currentTimeMillis() >= nextPrintTime) {
log("Hello ${i++}")
nextPrintTime += 1000L
}
}
}
log("job isActive: ${job.isActive}")
log("cancel job")
job.cancel()
log("job isActive: ${job.isActive}")
log("join job")
job.join()
}
}
输出:
job isActive: true
cancel job
job isActive: false
join job
6. 取消之后的收尾工作
在协程取消后,你可能需要做一些清理工作,例如关闭资源或其他清理操作。Kotlin 提供了三种方式来执行这些收尾工作。
方式 1:检查 !isActive
通过定期检查 isActive,你可以在协程取消后进行必要的清理。
方式 2:try-catch-finally
由于取消操作会抛出 CancellationException,你可以使用 try-catch-finally 结构来捕获并在 finally 块中进行清理。
方式 3:suspendCancellableCoroutine 和 invokeOnCancellation
invokeOnCancellation 方法可以帮助你在协程取消时自动执行清理操作。
示例:
suspend fun work() {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
log("Cleanup done!")
}
// rest of the implementation
}
}
7. 总结
通过理解如何正确地取消协程并处理取消后的清理工作,你可以有效地避免不必要的资源浪费,并确保协程的行为符合预期。为了实现高效的协程管理,建议在协程中定期检查其活动状态,特别是在执行长期任务时。同时,使用 suspendCancellableCoroutine 和 ensureActive() 等方法可以帮助你更好地配合协程的取消机制。
在 Android 开发中,使用 viewModelScope 或 lifecycleScope 可以简化协程管理,避免手动取消作用域。然而,开发者仍然需要确保在协程中正确处理取消,以便最大程度地提高应用性能和用户体验。
🌟 关注我的CSDN博客,收获更多技术干货! 🌟
更多推荐
所有评论(0)