- kotlin 协程 API
- 创建支持 kotlinx.coroutines 的项目
- 第一个协程程序
- launch 函数
- Job 对象
- runBlocking 函数
- 挂起函数
kotlin 协程 API
Kotlin 支持协程,并提供了丰富的协程编程所需的 API,主要是三个方面的支持:
(1) 语言支持。kotlin 语言本身提供一些对协程的支持,例如 kotlin 中的 suspend
关键字可以声明一个挂起函数。
(2) 底层 API。kotlin 标准库中包含协程编程核心底层 API,来自于 kotlin. coroutines
包,这些底层 API 虽然也可以编写携程代码,但是使用起来非常麻烦,不推荐直接使用这些底层 API。
(3) 高级 API。高级 API 使用起来很简单,但 kotlin 标准库中没有高级 API,它来自于 kotlin 的扩展项目 kotlinx.coroutines
框架(https://github.com/Kotlin/kotlinx.coroutines),使用时需要额外配置项目依赖关系。
创建支持 kotlinx.coroutines 的项目
kotlinx.coroutines
提供了协程开发的高级 API,使用起来比标准库中的底层 API 要简单得多。但使用 kotlinx.coroutines
需要额外在项目中配置依赖关系。下面是在 build.gradle
文件中添加 kotlinx.coroutines
依赖关系的配置。
apply plugin: 'java-library'
apply plugin: 'kotlin'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines" // 1️⃣
}
sourceCompatibility = "8"
targetCompatibility = "8"
buildscript {
ext.kotlin_version = '1.3.31' // 2️⃣
ext.kotlinx_coroutines = '1.3.0-M1'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
上述代码第1️⃣行的 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines"
是刚刚添加的依赖关系。另外,还需要检测代码第2️⃣行的 ext.kotlin_version
是否为最新的 kotlin 版本。
第一个协程程序
协程是轻量级的线程,因此协程也是由主线程管理的,如果主线程结束那么协程也就结束了。
fun main(args: Array?) {
GlobalScope.launch { // 1️⃣
for (i in 1..10) {
println("子协程执行第${i}次")
val sleepTime = (random() * 1000).toLong()
delay(sleepTime) // 2️⃣
}
println("子协程执行结束")
}
sleep(10 * 1000) // 3️⃣
println("主程序结束...")
}
运行结果:
子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...
上述代码第1️⃣行的 GlobalScope.launch
函数创建并启动了一个协程,类似于线程的 thread
函数。代码第2️⃣行的 delay
函数是挂起协程,类似于线程的 sleep
函数,但不同的是 delay
函数不会阻塞线程,而 sleep
函数会阻塞线程。代码第3️⃣行时候让主线程休眠 10s。如果这里主线程不休眠,主线程就直接结束了,其他的线程或协程没有机会运行。
launch 函数
上面示例中的 GlobalScope.launch
函数是非常重要的,它的定义如下:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
CoroutineScope
可以理解为协程本身,包含了CoroutineContext
。context
协程上下文,是一些元素的集合,主要包括Job
和CoroutineDispatcher
元素,可以代表一个协程的场景。CoroutineDispatcher
协程调度器,决定协程所在的线程或线程池。它可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样协程就会运行于当前线程)。coroutines-core
中CoroutineDispatcher
有以下实现Dispatchers.Default
、Dispatchers.IO
,Dispatchers.Main
和Dispatchers.Unconfined
,Unconfined
就是不指定线程。launch
函数定义如果不指定CoroutineDispatcher
或者没有其他的ContinuationInterceptor
,默认的协程调度器就是Dispatchers.Default
,Default
是一个协程调度器,其指定的线程为共有的线程池,线程数量至少为 2 最大与 CPU 数相同。start
参数设置协程启动。block
参数是协程体,类似于线程体,协程执行的核心代码在此编写,在协程体中执行的函数应该都是挂起函数,例如delay
函数就是挂起函数。launch
函数的返回是一个 Job 对象,Job 是协程要执行的任务,可以将 Job 对象看作协程本身,所有对协程的操作都是通过 Job 对象完成的,协程的状态和生命周期都是通过 Job 反应出来的。
Job 对象
Job 对象中常用的属性和函数如下:
-
isActive
属性:判断 Job 是否处于获得状态。 -
isCompleted
属性:判断 Job 是否处于完成状态。 -
isCancelled
属性:判断 Job 是否处于取消状态。 -
start
函数:开始 Job。 -
cancel
函数:取消 Job。 -
join
函数:使当前协程处于等待状态,直到 Job 完成,join
是一个挂起函数,只能在协程体或其他的挂起函数中调用。
fun main(args: Array?) {
val job = GlobalScope.launch {
for (i in 1..10) {
println("子协程执行第${i}次")
val sleepTime = (random() * 1000).toLong()
delay(sleepTime)
}
println("子协程执行结束")
}
println(job.isActive)
println(job.isCompleted)
sleep(10 * 1000)
println("主程序结束...")
println(job.isCompleted)
}
// 运行结果:
true
false
子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...
true
runBlocking 函数
前面的例子为了保持其他线程处于活动状态,示例中都使用了 sleep
函数。sleep
函数是线程提供的函数,最好不要在协程中使用,应该使用协程自己的 delay
函数,但 delay
是挂起函数,必须在协程体 或 其他的挂起函数中使用。
fun main(args: Array?) = runBlocking {
GlobalScope.launch {
for (i in 1..10) {
println("子协程执行第${i}次")
val sleepTime = (random() * 1000).toLong()
delay(sleepTime)
}
println("子协程执行结束")
}
delay(10 * 1000) // 1️⃣
println("主程序结束...")
}
// 运行结果
子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...
上述代码将 main
代码放到 runBlocking
函数中,runBlocking
函数也是启动并创建一个协程,可以与顶层函数一起使用。代码第1️⃣行使用 delay
函数挂起主协程。
挂起函数
如果开发人员需要编写一个挂起函数,可以使用 suspend
关键字声明,语法如下:
suspend fun 函数名(参数列表): 返回类型 {
// 函数体
}
注意:挂起函数只能在协程体中 或 其他的挂起函数中调用,不能在普通函数中调用。
挂起函数不仅可以是顶层函数,还可以是成员函数和抽象函数,子类重写挂起函数后还应该是挂起的。
abstract class SuperClass {
suspend abstract fun run()
}
class SubClass: SuperClass() {
override suspend fun run() { }
}
上述代码 SubClass
类实现了抽象类 SuperClass
的抽象挂起函数 run
,重写后它还是挂起函数。
fun main(args: Array?) = runBlocking {
GlobalScope.launch {
run("job1")
}
GlobalScope.launch {
run("job2")
}
delay(10 * 1000)
println("主程序结束...")
}
suspend fun run(name:String) {
for (i in 1..10) {
println("子协程 ${name} 执行第${i}次")
val sleepTime = (random() * 1000).toLong()
delay(sleepTime)
}
println("子协程 ${name} 执行结束")
}
// 运行结果
子协程 job1 执行第1次
子协程 job2 执行第1次
子协程 job1 执行第2次
子协程 job1 执行第3次
子协程 job1 执行第4次
子协程 job2 执行第2次
子协程 job2 执行第3次
子协程 job1 执行第5次
子协程 job1 执行第6次
子协程 job1 执行第7次
子协程 job2 执行第4次
子协程 job2 执行第5次
子协程 job2 执行第6次
子协程 job1 执行第8次
子协程 job1 执行第9次
子协程 job2 执行第7次
子协程 job2 执行第8次
子协程 job1 执行第10次
子协程 job1 执行结束
子协程 job2 执行第9次
子协程 job2 执行第10次
子协程 job2 执行结束
主程序结束...