Kotlin Coroutines Notes (I)

对于 Kotlin 官方文档当中 coroutines 部分的内容的学习 (翻译)

在 Kotlin 当中,协程(coroutines)通过库 kotlinx.coroutines 来提供,相较于其它语言的协程实现,Kotlin 选择的方式更为没有侵入性,其所使用的 asyncawait 等只是库提供的函数,而非语言自身的关键字,因而能够有着更好的兼容性。另外 Kotlin 当中提供的挂起函数 (suspending function)的概念,能为 Kotlin 当中 coroutines 提供比 Java 和 Javascript 当中的 future and promise 更加直观,安全,不易错的抽象

suspend function

A suspending function is simply a function that can be paused and resumed at a later time. They can execute a long running operation and wait for it to complete without blocking.

所谓的挂起其实可以理解成这个函数将不会阻塞当前线程,调用一个函数后就丢到后面自己执行

A suspend function can't be called everywhere. It should only be called from a coroutine or another suspend function

Basic

Kotlin 当中 coroutine 的概念其实跟 Go 当中的比较类似,可以当作是一个轻量级的线程。不过 coroutine 并不绑定于某个具体的线程,其也可以在一个线程当中挂起,在另一个线程当中继续执行,所以可以理解为在基本线程上面的一层抽象的“轻量线程”。

当然其实初期我们也不太需要理解其具体如何实现,先学会怎么用更实际

example

1
2
3
4
5
6
7
fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) //  delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}
  • launch: coroutine builder,用于开启一个新的协程(coroutine)来执行代码块,开启协程代码与其余代码同时独立地执行。
  • delay: 暂停协程一定时间后再恢复执行,这里的暂停并不会阻塞底层
  • runBlocking: 开启一个 coroutine scope, 并阻塞至其中所有协程执行完

只有在 coroutine scope 当中才能够通过 launch 开启新的协程,凭借此限制 Kotlin 可以实现结构化的并发(structured concurrency),保证了 CoroutineScope 必然会等待其中所有子协程完成才完成。

控制协程与结果获取

  • 通过 launch 开启一个新协程后同时会返回一个 Job 的对象,跟 C# 当中的 Task 比较类似,可以通过改对象来对协程任务进行控制(暂停或取消)
  • 通过 async 可以获取一个 Deferred 对象,类似于 FuturePromise 对象,存储了计算过程,但延迟了获取结果。it promises the result sometime in the future.

两者使用的区别主要是开启的协程是否有返回值。Deferred 类型为继承 Job 的泛型类型

结果获取

  • Deferred 对象可以通过 await 方法阻塞等待结果返回
  • 对于多个 Deferred 对象可以通过对其所在的 List 当中调用 awaitAll 阻塞等待所有任务完成返回

Channel

Kotlin 当中使用 Channel 来进行协程间的通信,就概念上来讲,其实跟 Go 当中的 channel 是一致的:不同 coroutines 通过往 channel 当中传输与接收数据,进行通信。不过 Kotlin 当中 Channel 为接口,使用的时候也是想普通的对象初始化与函数调用,通过 sendreceive 方法进行数据传输,与 Go 当中使用 <- 等作为语言特性的使用方式不同。

Kotlin 当中的协程类型有 4 种

  • 无限制 channel:channel 当中存储数量无限制
  • 带缓冲 channel:限制 channel 当中可以存储的数量,当 send 的时候 channel 当中元素数量超过缓冲数量限制则会阻塞
  • Rendezvous channel:相当于缓冲数量为 0 的缓冲 channel,每次 send 后都必须要等有人 receive 才能停止阻塞
  • Conflated channel:缓冲容量为 1,但每次 send 并不会阻塞,而是会把里面有的元素给覆盖掉
1
2
3
4
val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(CONFLATED)
val unlimitedChannel = Channel<String>(UNLIMITED)