kotlin 协程需要注意的7个要点

协程的 cancel 不能中断线程

如果我们在协程中使用了线程的并发集合,比如LinkedBlockingQueueArrayBlockingQueue 等等,这时候我们调用了它的阻塞接口,此时我们无法使用协程的 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)

这是因为虽然我们创建的 CoroutineContextcancel 方法,但是其内部实现是通过获取 CoroutineContextJob 来 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

评论0

显示验证码
没有账号?注册  忘记密码?