添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

kotlin Suspend Function

1 什么是Suspend Function?

Suspend function是用 suspend 关键字修饰的函数,suspend function需要在协程中执行,或者在另一个suspend function内。

我们考虑下面这样的场景,用户先输入账号密码进行注册,然后再用账号密码替用户自动登录,接着账户信息保存到本地缓存,最后返回用户信息。

如果按照以往用RxJava的写法,我们会写出如下的代码:

fun newUser(userName: String, password: String, callback: Callback<User>) {
        // 注册请求
        httpClient.register { user ->
            // 登录请求
            httpClient.login(user) { user ->
                // 保存结果到本地
                dataBase.save(user)
                // 回调
                userResult.success(user)
    }

这样的rxjava链式调用,我们都再熟悉不过了,一层层的回调向下传递。

如果用了suspend function,我们就可以把它看成一个普通的方法,只是这个方法可以被挂起和在任务完成后恢复,这意味着我们可以将一个耗时任务放到suspend function中等它完成。这样带来的好处是不用再像上面那样写回调嵌套,而是可以顺序调用每一个异步方法,可以写出下面这样的代码:

suspend fun newUser(name: String, pwd: String): User {
        val registerUser = register(name, pwd)
        val user = login(registerUser)
        dataBase.save(user)
        return user
    suspend fun register(name: String, pwd: String): User
    suspend fun login(user: User): User

需要注意的是只需要在耗时方法上增加 suspend 标记,这样可以让阻塞操作变成非阻塞操作,如果只是一个普通方法调用,就会收到一个警告 Redundant 'suspend' modifier ,意思是这个方法可以不用变成suspend function。

// Redundant 'suspend' modifier
    suspend fun redundant() {
        print("redundant suspend")
    suspend fun redundant() {
        withContext(Dispatchers.Default) {
            print("suspend")
    }

2 suspendCancellableCoroutine

在kotlin之前,异步请求往往会采用回调函数,将异步线程的数据回调回主线程。

在kotlin中则可以使用 suspendCancellableCoroutine 将回调函数转换成协程, suspendCancellableCoroutine 方法返回了 CancellableContinuation 实例。

public interface CancellableContinuation<in T> : Continuation<T> {
    public val isActive: Boolean
    public val isCompleted: Boolean
    public val isCancelled: Boolean
    public fun cancel(cause: Throwable? = null): Boolean
    @ExperimentalCoroutinesApi // since 1.2.0
    public fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?)
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))
 * Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
 * last suspension point.
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

CancellableContinuation有三种状态 | State | [isActive] | [isCompleted] | [isCancelled] | | ----------------------------------- | ---------- | ------------- | ------------- | | Active (initial state) | true | false | false | | Resumed (final completed state) | false | true | false | | Canceled (final completed state)| false | true | true |

下面是三种状态之间的转换关系:

+-----------+   resume    +---------+
   |  Active   | ----------> | Resumed |
   +-----------+             +---------+
         | cancel
   +-----------+
   | Cancelled |
   +-----------+

看一个简单的例子,讲解都在注释中,很好理解,用resume和resumeWithException就可以在协程里用回调写法。

MainScope().launch {
  try {
    val user = fetchUser()
    updateUser(user)
  } catch (exception: Exception) {
    // 捕获resumeWithException抛出的异常
private suspend fun fetchUser(): User = suspendCancellableCoroutine { 
cancellableContinuation ->
  // 异步网络请求
  fetchUserFromNetwork(object : Callback {
    override fun onSuccess(user: User) {
      // 回调获得的数据,最后会被fetchUser()方法声明的val user变量接收到
      cancellableContinuation.resume(user)
    override fun onFailure(exception: Exception) {
      //网络请求失败,抛出异常会被上面的try/catch块捕获
      cancellableContinuation.resumeWithException(exception)
private fun fetchUserFromNetwork(callback: Callback) {
  Thread {
    Thread.sleep(3_000)
    //模拟网路响应的回调
    callback.onSuccess(User())
  }.start()
private fun updateUser(user: User) {
  // 更新ui