Kotlin--suspendCancellableCoroutine和suspendCoroutine的区别及使用

站在巨人的肩膀上:https://medium.com/swlh/kotlin-coroutines-in-android-suspending-functions-8a2f980811f8

前言:

① 使用 suspendCancellableCoroutine 和 suspendCoroutine 可以将回调函数转换为协程
② SuspendCancellableCoroutine 返回一个 CancellableContinuation, 它可以用 resume、resumeWithException 来处理回调 和抛出 CancellationException 异常。它与 suspendCoroutine的唯一区别就是 SuspendCancellableCoroutine 可以通过 cancel() 方法手动取消协程的执行,而 suspendCoroutine 没有该方法。
③ 尽可能使用 suspendCancellableCoroutine 而不是 suspendCoroutine ,因为协程的取消是可控的

举个例子:使用网络请求数据时,如果请求时间过长,用户可以手动取消掉协程的执行。这时会抛出一个 CancellationException 异常,但是将该异常try{}catch{}捕获后就不会影响后续代码的执行。而使用 suspendCoroutine 只能干等着被 resume 或者 resumeWithException ,因为它没有该功能。

注:
① Kotlin没有检查异常,但是我们仍然需要在try-catch中处理所有可能的异常。否则,该应用程序将崩溃。但是 CancellationException 是个意外,它也会抛出异常,但是并不会导致程序崩溃。具体原因可参考 https://medium.com/swlh/kotlin-coroutines-in-android-basics-9904c98d4714
② SuspendCancellableCoroutine 的 cancel() 方法理解:调用 cancel() 后协程不再往下执行,抛出 CancellationException 异常,但是程序不会崩溃。而suspendCoroutine没有该方法,因此只能傻等…直到被通知 resume 或 resumeWithException。

在很多情况下,对于耗时操作,我们只需要将其声明为suspend函数并使用withContext(Dispatchers.IO)将其切换到IO线程即可。这样,耗时操作就会在IO线程下执行并在任务完成后,从挂起点继续往下执行。这是协程的基本写法,举个例子:
在这里插入图片描述

但是假设我们已经在线上有一个Android项目。许多异步任务用于等待读取数据库或从服务器获取数据。使用回调函数可能是一种处理主线程上数据的方法。我们都知道,回调写多了自己都能看晕,而且无法保持协程(用同步的方式写异步代码)这一要求。
这时候,suspendCancellableCoroutine就派上用场了。它就是专门用来将回调函数转换成协程的作用域

将回调函数转换为协程:suspendCancellableCoroutine

cancellableContinuation.resume

让我们来分析一下,这里使用 suspendCancellableCoroutine 声明了作用域,并在里面执行耗时操作(fetchUserFromNetwork)。该耗时操作接收一个 Callback,这其实就是我们熟悉的回调写法。
创建匿名内部类(object : Callback),如果成功返回,即回调 cancellableContinuation.resume(user) 方法,如果出现异常,就回调 cancellableContinuation.resumeWithException(exception) 方法。
在这里插入图片描述

其实说到底很简单,就是将回调交给 CancellableContinuation 去处理。而内部是怎么处理的,用户并不需要关心。用到的还是之前的回调逻辑,成功就是 onSuccess() 方法,失败就是 onFailure() 方法。只是我们在这里将回调的写法转换成了协程,即 onSuccess -> resume , onFailure -> resumeWithException。
当resume处理完之后,就会将处理完的值赋值给 fetchUser() 这个挂起函数,接着主线程就可以继续往下执行 updateUser(user)。即从挂起点继续往下执行。

cancellableContinuation. resumeWithException

注:为了演示 resumeWithException 是如何处理异常的,我们手动抛出了一个异常。此时 resumeWithException(exception) 就会执行,但是由于我们在主线程中 try{}catch{}捕获了异常,所以程序能够正常往下执行。
cancellableContinuation.

cancellableContinuation.cancel

还是上面那个例子,做一下改动,我们在 fetchUser 方法末尾手动 cancel 掉任务,看看会发生什么
在这里插入图片描述

还记得我们前面说过的 cancel() 方法的作用吗?这里再回顾一下,Kotlin没有检查异常,但是我们仍然需要在try-catch中处理所有可能的异常。否则,该应用程序将崩溃。但是 CancellationException 是个意外,它也会抛出异常,但是并不会导致程序崩溃。如果我们对该异常进行捕获,则协程能继续往下执行。如果不捕获异常,则终止该协程的后续操作。

### Kotlin 中 CoroutineScope 的用法 在 Kotlin 协程中,`CoroutineScope` 是管理一组相关协程的核心组件之一。它不仅定义了一个协程的作用域范围,还提供了启动新协程的能力[^1]。 #### 创建 `CoroutineScope` 可以通过扩展函数 `coroutineScope()` 或者直接实例化 `CoroutineScope` 来创建作用域: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val scope = CoroutineScope(Job()) // 启动一个新的协程在这个作用域下 scope.launch { delay(1000L) println("World!") } println("Hello") } ``` 上述代码展示了如何通过 `CoroutineScope` 启动新的协程,并且这些协程会自动关联到该作用域下的生命周期管理。 #### 使用 `runBlocking` `coroutineScope` 虽然两者都可以用来运行协程,但它们的行为有所不同。`runBlocking` 阻塞当前线程直到完成所有的子协程,而 `coroutineScope` 则不会阻塞主线程而是等待其内部的所有协程执行完毕后再继续。 下面是一个对比的例子: ```kotlin import kotlinx.coroutines.* fun main() { // 运行一个阻塞的主程序 runBlocking { launch { delay(200L) println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested coroutineScope") } } println("Coroutines are done in runBlocking") } } ``` 这段代码说明了即使外部有多个并发操作,`coroutineScope` 也会等到所有内部任务完成后才退出。 #### 性能优化考虑 为了提高性能并减少内存消耗,在实现挂起功能时应尽量避免不必要的对象分配。Kotlin 编译器采用状态机的方式来处理挂起表达式,这意味着无论存在多少个挂起点,都只会生成单一的状态类来表示整个逻辑流[^2]。 另外,当封装基于回调的 API 成为挂起函数时可以利用库提供的工具如 `suspendCoroutine()` 及其可取消版本 `suspendCancellableCoroutine()` 实现转换[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值