协程的 cancel 不能中断线程
如果我们在协程中使用了线程的并发集合,比如LinkedBlockingQueue
,ArrayBlockingQueue
等等,这时候我们调用了它的阻塞接口,此时我们无法使用协程的 cancel
来中断当前线程。这是因为在 Java 多线程中,我们需要使用 interrupt
方法才能中断线程,而 cancel 是不行的。kotlin 官方考虑到了这个问题,它提供了方便的 runInterruptible
方法。代码示例如下:
class Test {
suspend fun test() {
runInterruptible { // 内部会调用 interrupt 方法
while (true){
Thread.sleep(100)
println("do something")
}
}
}
}
fun main(): Unit = runBlocking {
val t = Test()
val job = launch(Dispatchers.IO) {
t.test()
}
delay(500)
job.cancel() // 这里只需要 cancel 就可以了
delay(1000)
println("end")
}
Job 的 cancel 才会取消协程
如下代码所示,我们创建一个 CoroutineContext
来启动一个协程,然后等待一段时间后 cancel
协程。执行后,我们可以发现 cancel
是没有效果的。
val threadContext = newSingleThreadContext("newThread")
CoroutineScope(threadContext).launch {
while (true) {
println("do something")
delay(500)
}
}
handler.postDelayed({
threadContext.cancel() // 没有效果
println("threadContext cancel")
}, 5000)
这是因为虽然我们创建的 CoroutineContext
有 cancel
方法,但是其内部实现是通过获取 CoroutineContext
的 Job
来 cancel 的。由于我们没有添加 Job,导致获取不到对应的 Job 对象,因此 cancel 没有执行。
/**
* Cancels [Job] of this context with an optional cancellation cause.
* See [Job.cancel] for details.
*/
public fun CoroutineContext.cancel(cause: CancellationException? = null) {
this[Job]?.cancel(cause)
}
如果你对 CoroutineContext
还不熟悉,可以看看 kotlin 协程入门教程 这篇文章。在文章中我们介绍过,CoroutineContext
的功能类似一个 Map,内部包含多个元素;而每一个元素都是 CoroutineContext
的实现,结构如下图所示:
创建新 Job 时注意不要破坏协程的结构化
当我们同时使用 CoroutineScope 和设置新的 CoroutineContext(包含Job) 时,哪个才是有效的?答案是后者,代码示例如下:
val newContext = threadContext + Job()
lifecycleScope.launch(newContext) {
while (true) {
println("do something")
delay(500)
}
}
handle.postDelayed({
lifecycleScope.cancel() // 无效
println("lifecycleScope cancel")
}, 1000)
handler.postDelayed({
newContext.cancel() // 有效
println("threadContext cancel")
}, 5000)
这是因为我们设置新的 Job 破坏了之前的结构。之前的结构是 lifecycleScope 的 Job 为 parent;而现在 新创建的 Job 为 parent。因此在设置新的 CoroutineContext 时需要注意,是否会影响到之前协程的结构。
挂起函数应该考虑协程取消
和 Java 的 interrupt 方法类似,kotlin 中的挂起函数默认是不会主动处理协程取消的。这需要你主动调用 ensureActive
方法,或者判断 isActive
。
suspend fun testCancel() {
while (true) {
coroutineContext.ensureActive()
println("do something")
}
}
suspend fun testCancel() {
while (coroutineContext.isActive) {
println("do something")
}
}
协程中try-catch异常时考虑 CancellationExceptions
当我们调用 cancel 方法来取消协程时,其内部是通过抛出 CancellationExceptions 的异常来实现的。如下代码所示,如果我们使用 runCatching 或者 try-catch(e: Exception) 包裹协程方法时,如果此时取消协程,不是直接退出,而是继续执行。
lifecycleScope.launch(Dispatchers.IO) {
runCatching {
// do something
while (true) {
delay(500)
println("do something1")
}
}.onFailure {
println(it)
}
while (true) { // 执行这里,无法退出
println("do something2")
}
}
不要在子协程中设置 CoroutineExceptionHandler
使用 CoroutineExceptionHandler 处理复杂结构的协程异常时,它仅在顶层协程中起作用。
val handle = CoroutineExceptionHandler { coroutineContext, throwable ->
println("处理异常")
}
GlobalScope.launch(Dispatchers.IO) {
launch {
delay(200)
println("1")
}
launch(handle) { // 无效
delay(100)
throw NullPointerException()
}
}
正确代码示例如下:
val handle = CoroutineExceptionHandler { coroutineContext, throwable ->
println("处理异常")
}
GlobalScope.launch(Dispatchers.IO + handle) { // 放在顶层协程中才能起作用
launch {
delay(200)
println("1")
}
launch() {
delay(100)
throw NullPointerException()
}
}
使用 repeatOnLifecycle 而不是 launchWhenXXX
在 Android 中,推荐使用 repeatOnLifecycle 而不是 launchWhenXXX 来处理不同不同生命周期的事件。
lifecycleScope.launchWhenCreated {
// do something
}
lifecycleScope.launch(Dispatchers.IO) {
lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
// do something
}
}
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22780,转载请注明出处。
评论0