写点什么

Kotlin 协程使用手册

用户头像
Android架构
关注
发布于: 14 小时前

标题的说法可能不太准确,但也能一窥其功用。协程是工作在线程之上的。我们知道线程是由系统(语言系统或者操作系统)进行调度的,切换时有着一定的开销。而协程,它的切换由程序自己来控制,无论是 CPU 的消耗还是内存的消耗都大大降低。


从这一点出发,它的应用场景可能就在于提高硬件性能的瓶颈。譬如说,你启动十万个协程不会有什么问题,但你启动十万个线程试试?

可暂停的程序

相较于第一点,这才是协程的本质;同时也是由这一点,协程发挥了很大的作用。在协程中,某段代码可以暂停,转而去执行另外的协程代码;被暂停的代码也可以在你的控制下随时恢复运行。


这在前端编程中有一个很大的用处——避免回调地狱。就 Android 编程而言,在 Rx 之前,要获取某个异步操作的返回结果,标准做法就是定义接口,用回调来接收结果。而 Rx 出现之后,以其巧妙的转换,通过响应式的代码,以一层的回调(辅以 lambda 表达式,看起来就像没有回调一样)链解决了回调地狱的问题。但在这里,习惯以命令式写法写代码的同学就需要稍稍理解一些函数式的编程思维了。协程不一样,它的代码是可以暂停的!也就是说,在我通过 getUser() 方法异步获取数据的时候,调用它的代码块就可以选择挂起,等到获取到数据,再恢复运行。代码看起来就这样:


val user = getUser() // 这儿的 getUser 就是 suspend function


是不是和同步代码看起来一样?


写过 JS 的同学可能就觉着很眼熟了:


async function getUser() {try {const response = await fetchUser();// ...} catch (e) {// error handle}}


没错,通过协程,Kotlin 是可以写出类似代码来的!

协程的使用

协程的骨架

首先,需要通过构造器来启动协程。官方目前提供的基础构造器有两个:


  • launch

  • runBlocking


它们都会启动一个协程,区别在于前者不会阻塞当前线程,并且会返回一个协程的引用,而后者会等待协程的代码执行结束,再执行剩下的代码。


其次,关于协程,Kotlin 新增了一个关键字:suspend ,被该关键字修饰的函数/方法/代码块只能由协程代码(也就是上述构造器的代码块参数内部)或者被 suspend 修饰的函数/方法/代码块调用。说简单一点,suspend fun 只能被 suspend fun 调用(协程构造器的最后一个参数的类型声明就是 suspend CoroutineScope.() -> Unit)。


知道了这两点,就可以写出最简单的协程代码:


fun main(args: Array<String>) {repeat(100_000) { // 启动十万个协程试试 launch { suspendPrint() }}Thread.sleep(1200) // 等待协程代码的结束}


suspend fun suspendPrint() {delay(1000)println(".")}


其中的 delay 就是一个 suspend fun


除了以上两点,另一个很重要的概念就是上下文context)。协程虽然是依赖于线程的,但一个协程并非就绑死在一个线程上。启动协程的时候可以指定上下文,在协程内部也可以通过 withContext 切换上下文。而这个上下文,也就是一个 CoroutineDispatcher 类的对象,从名字可以看出,就是由它去进行协程调度。比如,如果你需要新建一个线程去跑协程的代码,可以这样:


launch(context = newSingleThreadContext("new-thread")) { delay(1000) }


以上三点是我个人认为重要的内容,当然还有协程的取消、协程的生命周期、协程与子协程的关系等等,这些要点可以去官方文档或者我的翻译查看,内容写得很棒。

常规操作

async 与 await

就我个人所知,asyncawait 作为 JS 与 C# 的两个关键字,精简了异步操作(当然,这两门语言的细节并不一样)。但是在 Kotlin 中,async 其实是一个普通的函数:


fun main(args: Array<String>) = runBlocking<Unit> {val result: Deferred<String> = async { doSomethingTimeout() }println("I will got the result ${result.await()}")}


suspend fun doSomethingTimeout(): String {delay(1000)return "Result"}


在这里, async 代码块会新启动一个协程后立即执行,并且返回一个 Deferred 类型的值,调用它的 await 方法后会暂停当前协程,直到获取到 async 代码块执行结果,当前协程才会继续执行。


其实谈到这个,就不得不提一下 Retrofit 了,作为 RESTful 架构的优秀解决方案,有人已经为其适配了协程版的 adapter 了。我知道的有两个:



其实前者并不是 Retrofit 的 Adapter,Andrey Mischenko 只是为 Call 类添加了扩展函数而已。但是它们都是使用 Deferred 对象来处理结果。

channel 相关

这儿有个 channel 的概念,顾名思义,它的作用就在于收发事件。调用它的 sendreceive 方法,就是最简单的使用了。不过要注


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


意,这两个方法会互相等待,所以它们肯定得运行在不同的协程。


fun main(args: Array<String>) = runBlocking<Unit> {val channel = Channel()launch {for (x in 1..5) channel.send(x)channel.close()}for (x in 1..5) println(channel.receive())// or for (x in channel) println(x)}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Kotlin 协程使用手册