注:编码工具为IntelliJ
目录
依赖
使用协程前,需要导入协程依赖。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
Android中需要额外导入依赖。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
启动
package step_twelve
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.text.SimpleDateFormat
private fun Long.date() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this)
private fun log(msg: String){
println("[${System.currentTimeMillis().date()}]-[${Thread.currentThread().name}] $msg")
}
private class StartCoroutines{
fun t(){
runBlocking {
log("runBlocking 方式启动一个协程")
}
GlobalScope.launch {
log("launch 方式启动一个协程")
}
GlobalScope.async {
log("async 方式启动一个协程")
}
}
}
fun main() {
StartCoroutines().t()
}
输出:
[2021-11-24 10:42:48]-[main] runBlocking 方式启动一个协程
[2021-11-24 10:42:48]-[DefaultDispatcher-worker-1] launch 方式启动一个协程
[2021-11-24 10:42:48]-[DefaultDispatcher-worker-1] async 方式启动一个协程
可以从输出看出,runBlocking方式启动协程直接在协程启动的线程中执行协程体,而launch和async方式启动协程则另外开辟一个线程执行协程体。
几种启动方式的区别
阻塞与非阻塞
package step_twelve
import kotlinx.coroutines.*
fun main() {
log("before")
runBlocking {
delay(2000)
log("runBlocking")
}
GlobalScope.launch {
delay(2000)
log("launch")
}
GlobalScope.async {
delay(2000)
log("async")
}
log("after")
}
输出:
[2021-11-24 10:56:26]-[main] before
[2021-11-24 10:56:29]-[main] runBlocking
[2021-11-24 10:56:29]-[main] after
delay函数相当于Thead的sleep。
从输出可以看出,runBlocking阻塞了当前线程,先睡眠2秒,然后才继续执行协程体,执行结束继续执行main函数的后续代码,而launch和async因为不是阻塞式协程,启动后,main函数后续代码和协程体会同时执行,main函数因为没有睡眠,先执行结束了,而launch和async还在睡眠,所以log没有输出。
如果想要launch和async先执行完,然后main函数才执行完,可以让main函数睡眠一会。
package step_twelve
import kotlinx.coroutines.*
fun main() {
log("before")
runBlocking {
delay(2000)
log("runBlocking")
}
GlobalScope.launch {
delay(2000)
log("launch")
}
GlobalScope.async {
delay(2000)
log("async")
}
Thread.sleep(3000)
log("after")
}
输出:
[2021-11-24 11:04:35]-[main] before
[2021-11-24 11:04:37]-[main] runBlocking
[2021-11-24 11:04:39]-[DefaultDispatcher-worker-2] launch
[2021-11-24 11:04:39]-[DefaultDispatcher-worker-1] async
[2021-11-24 11:04:40]-[main] after
返回值
package step_twelve
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
suspend fun main() {
println("runBlocking 返回值是 ${runBlocking { log("runBlocking") }}")
println("runBlocking 返回值是 ${runBlocking { "runBlocking" }}")
println("launch 返回值是 ${GlobalScope.launch { log("launch") }}")
println("launch 返回值是 ${GlobalScope.launch { "launch" }}")
println("async 返回值是 ${GlobalScope.async { log("async") }}")
println("async 返回值是 ${GlobalScope.async { "async" }}")
println("async 返回值是 ${(GlobalScope.async { "async" }).await()}")
}
输出:
[2021-11-24 11:12:17]-[main] runBlocking
runBlocking 返回值是 kotlin.Unit
runBlocking 返回值是 runBlocking
[2021-11-24 11:12:17]-[DefaultDispatcher-worker-1] launch
launch 返回值是 StandaloneCoroutine{Active}@53e25b76
launch 返回值是 StandaloneCoroutine{Active}@73a8dfcc
async 返回值是 DeferredCoroutine{Active}@1b701da1
[2021-11-24 11:12:17]-[DefaultDispatcher-worker-1] async
async 返回值是 DeferredCoroutine{Active}@726f3b58
async 返回值是 async
从输出可以看出,runBlocking协程体的最后一行执行结果就是该方式启动的协程的返回值,而launch和async方式启动的协程默认返回协程对象。async方式启动的协程返回的对象可以通过await方法获得该协程的最后一行执行结果,这也是async和launch不同的地方。
小结:
runBlocking方式启动的协程会阻塞当前线程,launch和async方式启动的协程则不会;
runBlocking方式启动的协程的协程体最后一行执行结果就是协程的返回值,launch和async方式启动的协程默认会返回一个协程对象,async方式启动的协程返回的协程对象可以通过await函数获得返回值。
要调用async方式启动的协程返回的协程对象的await函数,主调函数必须声明为suspend的,所以上面代码中,main函数被声明为suspend的。
自定义作用域
协程必须有作用域才可以运行,可以自定义协程的作用域。
package step_twelve
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
fun main() {
val scope = CoroutineScope(EmptyCoroutineContext)
scope.launch {
log("自定义作用域")
}
val scope2 = object : CoroutineScope{
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
scope2.async {
log("自定义作用域")
}
Thread.sleep(100)
}
输出:
[2021-11-24 11:26:12]-[DefaultDispatcher-worker-2] 自定义作用域
[2021-11-24 11:26:12]-[DefaultDispatcher-worker-1] 自定义作用域
这里仿照GlobalScope自定义了Scope,具体自定义作用域有什么用处,还有待探索。
总结:
runBlocking主要用于测试,该函数的设计目的是让用suspend风格编写的库能够在常规阻塞代码中使用,常在main函数和测试代码中使用。
GlobalScope.launch/async不建议使用,因为这种启动方式启动的协程的生命周期是全局的,存在启动协程的组件已经被销毁但协程依然存在的情况,容易发生内存泄漏和内存溢出问题,尤其是移动应用这种需要频繁创建销毁组件的场景。