一、launch
launch()中的block()是挂起函数,不会阻塞当前线程。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {}
1.1、使用样例
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1协程执行=main, id=2
结论:lauch 是非阻塞的
1.2、多个launch情况
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1-1协程执行—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
GlobalScope.launch (Dispatchers.Main){
delay(2000)
println("2协程执行—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
}
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("3协程执行—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
}
println("1-2协程执行—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
}
println("主线程—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
1-2协程执行=main, id=2
3协程执行=main, id=2
2协程执行=main, id=2
结论:多个launch()是并行执行
1.3、join阻塞
如果需要等待 launch 开启的协程执行完毕,可以使用 join() 方法。join() 会阻塞当前线程,等待协程执行完毕后才释放。
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
GlobalScope.launch (Dispatchers.Main){
delay(2000)
println("2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}.join()
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("3协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}.join()
println("1-2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
2协程执行=main, id=2
3协程执行=main, id=2
1-2协程执行=main, id=2
注意:
join必须在suspend函数中或者协程作用域中使用。
二、runBlocking
runBlocking是常规函数,会把当前主线程包装成一个主协程,其会阻塞当前线程,只有当等待其主协程体以及里面的所有子协程执行结束以后,才会让当前线程执行。
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {}
2.1、使用样例
runBlocking {
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
1-1协程执行=main, id=2
主线程=main, id=2
结论:runBlocking 是阻塞的,一般不使用该函数
三、withContext
withContext()中的block()是挂起函数
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {}
3.1、使用样例
GlobalScope.launch(Dispatchers.Main) {
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
withContext(Dispatchers.Main) {
delay(2000)
println("2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
withContext(Dispatchers.Main) {
delay(1000)
println("3协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("1-2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
2协程执行=main, id=2
3协程执行=main, id=2
1-2协程执行=main, id=2
结论:withContext的代码块是串行执行,同时通过线程池技术执行的任务在不同的线程中。和多个launch()+join()效果一致
四、async + await
4.1、使用样例
var job = GlobalScope.launch (Dispatchers.Main){
val result1 = async(Dispatchers.IO) {
delay(2000)
Log.d("test", "1协程执行—${Thread.currentThread().name}_id_${Thread.currentThread().id}")
}
val result2 = async(Dispatchers.IO) {
delay(1000)
Log.d("test", "2协程执行— ${Thread.currentThread().name}_id_${Thread.currentThread().id}")
}
Log.d("test","result ${result1.await()} ${result2.await()}")
}
日志输出
2协程执行— DefaultDispatcher-worker-1_id_865
1协程执行—DefaultDispatcher-worker-5_id_869
result 1 1
结论:async的代码块是并行执行,同时通过线程池技术执行的任务在不同的线程中
注意:
- 如果想要多个协程并发执行,并且获得多个返回值,可以先调用 async,最后统一调用 await(),这样能让协程并发执行。(await 会阻塞协程,所以这种写法会导致两个协程串行执行)
- 不要在每个协程 async 开启后,马上调用 await(),因为 await() 会阻塞当前线程。这样开启的多个协程会串行执行,效率较低。(统一调用 await 方法,让两个协程并行执行,效率高)
五、coroutineScope{ }
也可以
CoroutineScope()
5.1、使用样例
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
coroutineScope{
delay(2000)
println("2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
coroutineScope{
delay(1000)
println("3协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("1-2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
2协程执行=main, id=2
3协程执行=main, id=2
1-2协程执行=main, id=2
结论:coroutineScope{}的代码块是串行执行。和多个withContext()效果一致;区别在于withContext()可以切换线程,而coroutineScope{}不行。
六、supervisorScope{ }
6.1、使用样例
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
supervisorScope{
delay(2000)
println("2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
supervisorScope{
delay(1000)
println("3协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("1-2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
2协程执行=main, id=2
3协程执行=main, id=2
1-2协程执行=main, id=2
结论:supervisorScope{}的代码块是串行执行。和多个coroutineScope{}效果一致;区别在于coroutineScope{}是一个协程执行失败了,所有其他兄弟协程执行就会中断 (一毁俱毁),而supervisorScope{}是一个协程执行失败了,不会影响其他兄弟协程的执行 (一毁不俱毁)。
七、suspendCoroutine<?>{ }
7.1、使用样例
GlobalScope.launch (Dispatchers.Main){
delay(1000)
println("1-1协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
suspendCoroutine<Int>{
println("2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
it.resume(1)
}
suspendCoroutine<Int>{
println("3协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("1-2协程执行=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
}
println("主线程=${Thread.currentThread().name}, id=${Thread.currentThread().id}")
日志输出
主线程=main, id=2
1-1协程执行=main, id=2
2协程执行=main, id=2
3协程执行=main, id=2
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
//注意:
//后续没有调用 resume() 执行协程,所以创建完之后需要手动调用 resume()
safe.getOrThrow()
}
}
结论:
- 因为
suspendCoroutine()中的block()不是suspend挂起的,所以函数体内无法使用delay()之类的函数; - 如果没有主动调用
resume(),则协程无法往下回调执行; - kotlin协程库delay的具体实现,就是用
suspendCoroutine实现的。
八、总结
- lauch 是非阻塞的。
- runBlocking 是阻塞的**。**
- 多个 withContext 任务是串行的, 且withContext 可直接返回耗时任务的结果。
- 多个 async 任务是并行的,async 返回的是一个Deferred,需要调用其await()方法获取结果。
| 启动方式 | 运行在任何处 | 运行在suspend函数中 | 运行在CoroutineScope作用域里 |
|---|---|---|---|
| launch{ } | 是 | 是 | 是 |
| runBlocking{ } | 是 | 是 | 是 |
| withContext{ } | 否 | 是 | 是 |
| async{ } | 否 | 否 | 是 |
| coroutineScope{ } | 否 | 是 | 是 |
| suspendCoroutine<?> { } | 否 | 是 | 是 |
| supervisorScope{ } | 否 | 是 | 是 |