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

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

1. 概述



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


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


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


2. 恢复



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


image.png


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

image.png

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


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


image.png

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

image.png

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


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


image.png

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


image.png


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

image.png

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

image.png

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


image.png


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


3. 带值恢复



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


image.png

image.png


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


image.png


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


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


image.png

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


image.png


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


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


4. 异常恢复



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


image.png

image.png

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



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


image.png

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


image.png

相关文章
|
3月前
|
存储 Linux 调度
协程(coroutine)的原理和使用
协程(coroutine)的原理和使用
|
17天前
|
存储 安全 测试技术
GoLang协程Goroutiney原理与GMP模型详解
本文详细介绍了Go语言中的Goroutine及其背后的GMP模型。Goroutine是Go语言中的一种轻量级线程,由Go运行时管理,支持高效的并发编程。文章讲解了Goroutine的创建、调度、上下文切换和栈管理等核心机制,并通过示例代码展示了如何使用Goroutine。GMP模型(Goroutine、Processor、Machine)是Go运行时调度Goroutine的基础,通过合理的调度策略,实现了高并发和高性能的程序执行。
77 29
|
15天前
|
负载均衡 算法 Go
GoLang协程Goroutiney原理与GMP模型详解
【11月更文挑战第4天】Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理,创建和销毁开销小,适合高并发场景。其调度采用非抢占式和协作式多任务处理结合的方式。GMP 模型包括 G(Goroutine)、M(系统线程)和 P(逻辑处理器),通过工作窃取算法实现负载均衡,确保高效利用系统资源。
|
1月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
25 1
|
2月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
26 1
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
25 0
|
1月前
|
数据采集 调度 Python
Python编程异步爬虫——协程的基本原理(一)
Python编程异步爬虫——协程的基本原理(一)
|
1月前
|
数据采集 Python
Python编程异步爬虫——协程的基本原理(二)
Python编程异步爬虫——协程的基本原理(二)
|
1月前
|
存储 前端开发 rax
协程设计与原理(二)
协程设计与原理(二)
16 0
|
1月前
|
Java Linux Go
协程的设计原理(一)
协程的设计原理(一)
32 0