Kotlin的协程:挂起函数

挂起函数

挂起函数是指使用 suspend 关键字修饰的函数。

suspend fun getUserInfo(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "BoyCoder"
}

挂起和恢复

挂起函数与普通函数的区别在于:挂起函数可以挂起和恢复。挂起和恢复也是协程与线程相比的优势。

考虑下面一种场景:

  1. 获取用户信息
  2. 获取用户的好友
  3. 获取每位好友的动态

如果使用 Java,可能会这么写:

public class SuspendFunction {

    public void getAllFeeds() {
        getUserInfo(new Callback() {
            @Override
            public void onSuccess(String user) {
                if (user != null) {
                    System.out.println(user);
                    getFriendList(user, new Callback() {
                        @Override
                        public void onSuccess(String friendList) {
                            if (friendList != null) {
                                System.out.println(friendList);
                                getFeedList(friendList, new Callback() {
                                    @Override
                                    public void onSuccess(String feedList) {
                                        if (feedList != null) {
                                            System.out.println(feedList);
                                        }
                                    }
                                });
                            }
                        }
                    });
                }
            }
        });
    }

    private void getFeedList(String friendList, Callback callback) {
        new Thread(() -> {
            SystemClock.sleep(1000L);
            if (callback != null) {
                callback.onSuccess("feedList");
            }
        }).start();
    }

    private void getFriendList(String user, Callback callback) {
        new Thread(() -> {
            SystemClock.sleep(1000L);
            if (callback != null) {
                callback.onSuccess("friendList");
            }
        }).start();
    }

    private void getUserInfo(Callback callback) {
        new Thread(() -> {
            SystemClock.sleep(1000L);
            if (callback != null) {
                callback.onSuccess("user");
            }
        }).start();
    }

    public interface Callback {
        void onSuccess(String response);
    }
}

这种多重回调的模式被称为“回调地狱”,代码嵌套层次多,可读性差。

如果使用 Kotlin 的挂起函数改写,会变得很简单:

fun main() = runBlocking {
    val userInfo = getUserInfo()
    val friendList = getFriendList(userInfo)
    val feedList = getFeedList(friendList)
    println(feedList)
}

suspend fun getUserInfo(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "BoyCoder"
}

suspend fun getFriendList(user: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "friendList"
}

suspend fun getFeedList(friendList: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "feedList"
}

挂起函数的特点是使用同步的方式完成异步任务。

以下面的代码为例

val userInfo = getUserInfo()

getUserInfo 使用 withContext 切换到 IO 线程,延迟 1 秒,然后返回结果。程序在调用 getUserInfo 时挂起,然后返回结果给 userInfo 时恢复。不需要像 Java 使用回调来传递结果。

等号 “=” 右边的代码执行在子线程并挂起,右边执行完毕后,等号 “=” 左边的代码恢复到主线程执行。

深入理解 suspend

挂起函数的本质就是 Callback。Kotlin 的编译器会将 suspend 函数转换为带有 Callback 的普通函数。

反编译之前的 Kotlin 代码,可以看出 getUserInfo 挂起函数转换为了带有 Continuation 参数的普通函数。

@Nullable
public static final Object getUserInfo(@NotNull Continuation var0) {
    ...
}

Continuation 的定义如下:

public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

Continuation 是一个接口,带有 resumeWith 方法用来返回结果。本质和 Callback 的作用是一样的。

这种将 suspend 函数转换为带有 Continuation 普通函数的过程叫做 CPS 转换

CPS:Continuation-Passing-Style Transformation。

CPS转换就是将程序接下来要执行的代码进行传递的一种模式,它将原本的同步挂起函数转换为 Callback 异步代码。

协程之所以是非阻塞,是因为它支持“挂起和恢复”,而挂起和恢复的能力,主要来自挂起函数。挂起函数是由 CPS 实现的,其中的 Continuation,本质上是 Callback。

协程与挂起函数

协程的主要能力来自挂起函数,但是协程不等同于挂起函数。

挂起函数只能在协程或者其他挂起函数中调用。因为只有协程和其他挂起函数能够提供 Continuation。

从 runBlocking 的参数 block 可以看出,block 也是一个挂起函数。

public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {

因为挂起函数能够调用挂起函数,协程提供了 block 挂起函数,所以协程也能调用挂起函数。

挂起和恢复是协程的一种底层能力,而这种能力的实现,依靠的是挂起函数。

小结

  1. 挂起函数使用 suspend 关键字表示。
  2. 挂起函数能够以同步的方式写异步代码。
  3. 挂起函数拥有挂起和恢复的能力。
  4. 挂起函数的本质是 Callback,也就是 Continuation,Kotlin 编译器会完成 CPS 转换。
  5. 挂起函数只能在协程或者其他挂起函数中调用。
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值