深入理解Kotlin协程suspend工作原理(初学者也能看得懂)

1. 概述

挂起函数是Kotlin协程最重要的一个特性,所有其他概念都建立在它的基础上。所以我们需要深入了解它的工作原理。

挂起协程意味着在中间停止它。这类似于玩游戏,当我们想暂停游戏时,可以先存档,想继续游戏时,可以从存档处恢复游戏。当协程被暂停时,它会返回一个Continuation. 这就像游戏中的存档,协程可以使用Continuation从挂起的地方恢复。

请注意,这与线程非常不同,线程无法保存,只能阻塞。协程要强大得多,挂起时,它不消耗任何所在线程的资源。协程不一定需要在启动协程的线程上恢复,可以切换到不同的线程上。

2. 恢复

让我们看看它的实际效果。我们使用协程构建器(如runBlocking或launch)启动协程。suspend函数是可以挂起协程的函数。这意味着,它们必须在协程(或另一个挂起函数)上调用。

这是一个简单的程序,将打印“Before”和“After”。如果我们在中间调用协程标准库提供的suspendCoroutine函数会怎样呢?

如果你执行上面的代码,你将发现不会打印“After”,并且main函数不会退出。协程在“Before”之后暂停。我们的程序停止了,再也没有恢复。那我们怎么做呢?我们可以使用Continuation来恢复程序?

我们观察下suspendCoroutine调用,注意它以 lambda 表达式 ( { })结尾。作为参数传递的函数将在暂停之前被调用。此函数将Continuation作为参数。

观察打印日志,我们发现“After”仍然没有打印。我们可以用Continuation的resume方法让协程恢复执行。

请注意,上面示例中的“After”被打印出来,因为我们在suspendCoroutine中调用resume函数。

从 Kotlin 1.3 开始, Continuation的定义发生了变化。resumeWith函数代替了resume和resumeWithException函数。我们使用的resume和resumeWithException是标准库中的扩展函数。

我们还可以使用它来启动一个不同的线程,该线程将休眠一段时间后恢复:

我们还可以将启动线程的代码提取到一个函数中。

这种机制是有效的,但是我们也可以用线程池来代替线程。


暂停一段时间似乎是一个有用的功能。让我们把它提取成一个函数。我们将命名它delay。

这正是Kotlin协程库delay方法曾经的实现方式。目前的实现比较复杂,主要是为了支持测试,但是本质思路还是一样的。

3. 带值恢复

您可能需要注意的是我们为什么将Unit传递给resume函数,为什么我们将Unit作为suspendCoroutine函数泛型类型。Unit是suspendCoroutine函数的返回类型,也是Continuation的泛型类型。

当我们调用suspendCoroutine时,我们可以指定Continuation中的返回类型。resume函数需要调用与之相同的类型。

挂起对协程非常有意义。当我们做耗时操作时,我们需要被挂起。例如,当我们需要从API获取网络响应时,如果没有协程,这个线程就需要等待。因为线程很昂贵,这将是一个巨大的浪费。特别是当这是一个重要的线程,比如 Android 上的主线程。使用协程,它只是挂起协程,然后线程可以去做其他事情。一旦数据到了,线程将从协程挂起点恢复。

举个例子,我们模拟网络请求用户信息:


直接在main方法中调用suspendCoroutine不太方便。我们可以把它提取成一个方法。

目前,许多流行的库如Retrofit或Room已经支持suspend函数。这就是为什么我们很少需要在suspend函数中使用回调函数的原因。还有一个和suspendCoroutine类似的方法suspendCancellableCoroutine,我推荐你使用后者,后者支持取消功能。

你可能想知道如果API不给我们数据而是返回异常会怎样。如果服务死机或响应错误会怎样?。在这种情况下,我们不能返回数据,而是应该从协程挂起的地方抛出异常

4. 异常恢复

我们调用的每个函数都可能返回某个值或抛出异常。对于suspendCoroutine也是如此。我们可以调用resumeWithException从异常处返回。可以用try catch捕获异常。

5. 挂起一个协程,而不是一个函数

需要强调的一件事是我们只是暂停了一个协程,而不是一个函数。想象一下,我们将Continuation存储在某个变量中,并尝试在函数调用后恢复它。


这没有任何意义。resume永远不会被调用。

欢迎关注"字节小站"同名微信公众号。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kotlin协程是一种轻量级的并发框架,它可以在不创建新线程的情况下实现异步操作。Kotlin协程的实现原理是基于挂起函数和Continuation(协程上下文)。 在Kotlin协程中,挂起函数是指可以被暂停执行,并在稍后继续执行的函数。在挂起函数中,可以使用`suspend`关键字来标记该函数为挂起函数。当调用一个挂起函数时,该函数会返回一个`Continuation`对象,该对象可以被用来在稍后的时间点恢复挂起函数的执行。 Kotlin协程的调度器会在适当的时候调用Continuation对象的`resume`方法来恢复挂起函数的执行。当一个挂起函数被恢复执行时,它会从上一次挂起的地方继续执行,直到函数结束或者再次遇到挂起点。 Kotlin协程的实现原理可以用以下伪代码来说明: ```kotlin fun main() { launch { println("Hello") delay(1000) println("World") } } suspend fun delay(time: Long) { // 挂起当前协程,等待一段时间 // 通过Continuation对象来恢复协程的执行 suspendCoroutine<Unit> { continuation -> Timer().schedule(time) { continuation.resume(Unit) } } } fun launch(block: suspend () -> Unit) { // 创建一个新的协程,并将其加入到调度器中 val coroutine = Coroutine(block) coroutine.start() } class Coroutine(private val block: suspend () -> Unit) { fun start() { block.startCoroutine(this) } } class Continuation(private val coroutine: Coroutine) { fun resume(value: Any?) { coroutine.resume(value) } } suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T { // 挂起当前协程,等待Continuation对象被调用 // 通过Continuation对象来恢复协程的执行 return suspendCoroutineOrReturn { continuation -> block(Continuation(coroutine)) } } ``` 在上面的代码中,`launch`函数用于创建一个新的协程,并将其加入到调度器中。`Coroutine`类表示一个协程,`start`方法用于启动协程的执行。`suspendCoroutine`函数用于挂起当前协程,并等待Continuation对象被调用以恢复协程的执行。`delay`函数使用`suspendCoroutine`函数来实现挂起功能,并在一定时间后恢复协程的执行。 Kotlin协程的实现原理比较复杂,但是开发者只需要关注如何使用协程来实现异步操作即可。通过使用协程,开发者可以编写出更加简洁、易于理解、易于维护的异步代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值