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 函数(例如 withContextdelay)都是可取消的。如果使用这些函数,你无需显式地检查协程状态。否则,使用 isActiveensureActive() 来检查协程是否处于活动状态。

4. 检查 Job 的活动状态

要让协程响应取消操作,你可以使用 isActiveensureActive() 来定期检查协程的状态。

示例代码:

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:suspendCancellableCoroutineinvokeOnCancellation

invokeOnCancellation 方法可以帮助你在协程取消时自动执行清理操作。

示例:

suspend fun work() {
    return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
            log("Cleanup done!")
        }
        // rest of the implementation
    }
}

7. 总结

通过理解如何正确地取消协程并处理取消后的清理工作,你可以有效地避免不必要的资源浪费,并确保协程的行为符合预期。为了实现高效的协程管理,建议在协程中定期检查其活动状态,特别是在执行长期任务时。同时,使用 suspendCancellableCoroutineensureActive() 等方法可以帮助你更好地配合协程的取消机制。

在 Android 开发中,使用 viewModelScopelifecycleScope 可以简化协程管理,避免手动取消作用域。然而,开发者仍然需要确保在协程中正确处理取消,以便最大程度地提高应用性能和用户体验。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐