什么是协程
协程(Coroutine),本质上是更轻量级的线程。在一个线程上可以同时跑多个协程。与线程相比,它更轻量、资源占用更少。
suspend关键字
suspend关键字修饰方法,标识它是suspend function。suspend function有两个特点:
- 该方法只能在协程中被调用
- 该方法体内可以调用其他
suspend方法
像delay方法就是suspend方法,它只能在协程中调用,它的作用是阻塞协程。类似线程中的Thread.sleep方法。
如何创建一个协程
launch
通过launch方法创建协程
1
2
3
GlobalScope.launch {
//do something...
}
launch方法源码如下:
1
2
3
4
5
6
7
8
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
//...
return coroutine
}
可以看到,launch方法是挂靠在接口CoroutineScope上的方法。像上面的GlobalScope,就是接口CoroutineScope的一个实现类。
launch方法的生命周期与当前线程的生命周期一致。
launch方法有三个入参、一个出参,分别来分析下它们的作用:
context: CoroutineContext。CoroutineContext是一系列元素的集合,最主要的两个元素是:Job、Dispatcher。Job控制协程的开始、取消等,Dispatcher负责协程在哪个线程中执行。start: CoroutineStart。该入参主要是控制协程是直接执行还是
Lazy start。若是CoroutineStart.LAZY, 需要通过job.start方法主动开启协程。block: suspend CoroutineScope.() -> Unit。协程要执行的代码段。从定义看,
block是一个suspend匿名方法,且是挂靠在接口CoroutineScope下。所有,代码段中的
this关键字,直接代表了CoroutineScope。Job。Job是launch方法的返回值,它就是用来控制协程的运行状态的。Job中有几个关键方法:- start。如果是
CoroutineStart.LAZY创建出来的协程,调用该方法开启协程。 - cancel。取消正在执行的协程。如协程处于运算状态,则不能被取消。也就是说,只有协程处于阻塞状态时才能够取消。
- join。阻塞父协程,直到本协程执行完。
- cancelAndJoin。等价于
cancel+join。
- start。如果是
在block中,用try-finally来包裹代码段,当调用job.cancel时,代码段会执行到finally中。通常情况下,finally中不能够再调用suspend方法,否则会抛出CancellationException。但是也有例外,如:
1
2
3
4
5
6
7
8
9
try {
//do something
} finally {
//通过withContext(NonCancellable)来调用suspend方法
withContext(NonCancellable) {
delay(1000)
print("lalalalala")
}
}
runBlocking
通过runBlocking创建协程:
1
2
3
runBlocking {
//do something...
}
runBlocking会阻塞当前线程,直到它的协程结束。由runBlocking发起的协程,它的生命周期是它内部所有的协程都完成才算结束。
runBlocking的定义:
1
2
3
4
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
...
return coroutine.joinBlocking()
}
两个入参与方法launch的入参一致。
它有一个返回值,类型是T。返回值的调用写法如下:
1
2
3
4
5
6
7
8
9
fun main() {
val result = runBlocking<Int> {
delay(500)
1
}
println("result: $result")
}
//result: 1
async
通过async创建协程:
1
2
3
GlobalScope.async {
//do something
}
async源码如下:
1
2
3
4
5
6
7
8
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
//...
return coroutine
}
它与launch类似,差别在于返回值。async方法返回一个Deferred<T>类型。
Deferred继承自Job,最主要的是增加了await方法,通过await方法返回T。Deferred.await在等待返回值时会阻塞当前的协程。
async方法调用的例子:
1
2
3
4
5
6
7
8
9
10
11
fun main() {
runBlocking {
val result = async {
delay(1000)
1
}
print("result: ${result.await()}")
}
}
//result: 1
withContext
withContext接收一个CoroutineContext, 阻塞协程并等待协程返回T值。
1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() = runBlocking {
val result = withContext(this.coroutineContext) {
println("thread name: ${Thread.currentThread().name}")
1
}
println("result: $result")
}
/**
thread name: main
result: 1
**/
Dispatchers.Unconfined
在launch等方法调用时,可以设置CoroutineContext。Kotlin.coroutine库中实现了几种:
Dispatchers.Unconfined。发起的协程会在当前线程中执行。但只要阻塞之后,协程将在线程中恢复,该线程完全由调用的挂起线程决定。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
fun main() { runBlocking { val job = launch(Dispatchers.Unconfined) { println("launch unconfined thread before: ${Thread.currentThread().name}") delay(100) println("launch unconfined thread after: ${Thread.currentThread().name}") } job.join() launch { println("launch thread before: ${Thread.currentThread().name}") delay(100) println("launch thread after: ${Thread.currentThread().name}") } } } /** launch unconfined thread before: main launch unconfined thread after: kotlinx.coroutines.DefaultExecutor launch thread before: main launch thread after: main **/
Dispatchers.Default。 让发起的协程在默认的线程中允许。launch(Dispatchers.Default){}与GlobalScope.launch{}一样,都是在默认的线程中执行。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
fun main() { GlobalScope.launch { println("GlobalScope launch thread: ${Thread.currentThread().name}") } runBlocking { launch(Dispatchers.Default) { println("launch Default thread: ${Thread.currentThread().name}") } launch { println("launch thread: ${Thread.currentThread().name}") } println("current thread: ${Thread.currentThread().name}") } } /** GlobalScope launch thread: DefaultDispatcher-worker-1 launch Default thread: DefaultDispatcher-worker-2 current thread: main launch thread: main **/
Dispatchers.Main。让协程在android的主线程中执行。使用它需要添加额外的模块,kotlinx-coroutines-android。Dispatchers.IO。让协程在默认的共享线程池中执行。本质上与Dispatchers.Default共享一个线程池。
coroutineScope
使用coroutineScope创建一个新的CoroutineScope, 它的coroutineContext是由外部协程的coroutineContext提供的。
1
2
3
coroutineScope {
//do something
}
它的作用是并行分解工作的。将一部分关联的工作放入该coroutineScope中,若其中一个子协程报错了,其他的子协程都会被cancel掉。
同时,它执行时会阻塞协程,并等待返回值R。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fun main() = runBlocking {
try {
coroutineScope {
launch {
try {
delay(2000)
println("launch coroutine delay 2000 ms")
} finally {
println("launch coroutine is cancelled")
}
}
launch {
delay(1500)
throw Exception("test exception")
}
}
} catch (e: Exception) {
println(e)
}
println("end")
}
/**
launch coroutine is cancelled
java.lang.Exception: test exception
end
**/
withTimeout
通过withTimeout(millisecond) {},可以对协程做超时处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() = runBlocking {
val result = withTimeout(1000) {
delay(500)
1
}
println("result: $result")
val result2 = withTimeout(1000) {
delay(2000)
2
}
println("result2: $result2")
}
//result: 1
//Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
正常情况下,withTimeout等待协程返回值,并阻塞当前协程。当它发起的协程执行时间超过设定值时,会抛出异常TimeoutCancellationException。
可以使用withTimeoutOrNull代替withTimeout。它们的区别在于withTimeoutOrNull超时时不会抛出异常,而是返回Null。
将上面例子中的withTimeout替换成withTimeoutOrNull后的结果是:
1
2
//result: 1
//result2: null
协程的关系
不同方式产生的协程,彼此的关系如下图所示:
