启动协程:launch与async的区别、runBlocking的使用场景、GlobalScope的坑
协程怎么启动?说白了就三种方式:launch、async、runBlocking。还有一个GlobalScope,嗯,这个我建议你尽量别碰。
我记得刚学协程那会儿,看到这么多启动方式,头都大了。后来踩了几个坑,才慢慢摸清楚它们的脾气。今天咱们就把这几个东西彻底聊透。
launch vs async:一个点火,一个问路
先看个最简单的例子:
// launch:只管启动,不关心结果
fun main() = runBlocking {
val job = launch {
delay(1000L)
println("任务完成")
}
// job 是一个 Job 对象,可以用来等待或取消
job.join()
}
// async:启动并期待返回值
fun main() = runBlocking {
val deferred = async {
delay(1000L)
"计算结果"
}
// deferred 是一个 Deferred<String> 对象
val result = deferred.await()
println(result) // 输出:计算结果
}
看出区别了吗?launch就像你点了个火,烧完就完了。async呢,像是派了个人去办事,办完了还得回来跟你汇报结果。
核心区别一句话:
launch返回Job,不关心返回值async返回Deferred<T>,通过await()获取结果
我在项目中遇到过这样一个场景:同时请求三个接口,等全部返回后再做处理。用async就特别合适:
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
val three = async { doSomethingUsefulThree() }
// 三个任务并发执行,全部完成后汇总
println("结果:${one.await() + two.await() + three.await()}")
}
println("耗时:$time ms")
}
你想想看,如果换成launch,你得自己维护一堆变量来存结果,多麻烦。
runBlocking:桥接两个世界
runBlocking是个特殊的存在。它能把协程世界和普通代码世界连接起来。
fun main() {
println("开始")
runBlocking {
delay(1000L)
println("协程内执行")
}
println("结束")
}
运行结果:
开始
(等待1秒)
协程内执行
结束
注意看,runBlocking会阻塞当前线程,直到里面的协程全部执行完。这跟launch完全不一样——launch是非阻塞的。
⚠️ 使用场景要谨慎
runBlocking主要用在两个地方:
- main函数里,作为协程的入口
- 测试代码中,方便写单元测试
千万别在Android的主线程里用runBlocking,那会导致ANR。我曾经见过有人这么写,结果APP直接卡死……
GlobalScope:看起来方便,用起来坑
先看个例子:
fun main() {
GlobalScope.launch {
delay(1000L)
println("GlobalScope 执行")
}
println("main 结束")
// 程序直接退出了,协程里的代码根本没执行
}
为什么会这样?因为GlobalScope启动的协程生命周期跟整个应用绑定。main函数执行完了,JVM就退出了,协程还没来得及跑呢。
更坑的是什么呢?
GlobalScope 的三大问题:
- 生命周期不可控:协程跟应用同生共死,你没法精确控制它什么时候结束
- 容易造成内存泄漏:如果协程里持有Activity的引用,Activity销毁了协程还在跑
- 浪费资源:协程会一直占用线程池,即使没人关心它的结果
我曾经接手过一个项目,里面大量使用GlobalScope。结果呢?页面退出了,网络请求还在跑,回调回来时Activity已经没了,各种空指针异常。那段时间修bug修到怀疑人生……
💡 我的建议:
能用CoroutineScope就别用GlobalScope。在Android里,用lifecycleScope或viewModelScope,它们会自动管理生命周期。
实战对比:选哪个?
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 触发一次操作,不关心结果 | launch |
比如日志上传、埋点 |
| 需要并发执行并获取结果 | async |
比如多个接口并行请求 |
| main函数或测试 | runBlocking |
桥接协程和普通代码 |
| 全局任务 | 自定义CoroutineScope |
别用GlobalScope |
最后说一句:launch和async没有谁好谁坏,关键看你要什么。要结果就用async,不要结果就用launch。就这么简单。
嗯,关于启动协程的几种方式,今天就聊到这儿。下一节咱们聊聊协程的取消与超时,那个坑更多,到时候我慢慢跟你说。