1. 简介
Kotlin 协程的设计初衷是让创建协程变得简单。相比线程,协程更加轻量级,因此我们可以按需随时创建。
虽然你可能看到文档中说“协程就是轻量线程”,但这个说法并不完全准确。线程代表的是系统资源的分配以及其执行的指令序列;而协程只通过调度器(Dispatcher)弱引用这些资源,它更强调的是与调用者生命周期之间的关系。
如果调用者已经不存在了,继续执行协程也就没有意义。因此,一旦协程的结果不再需要,最好立即取消它。这就是结构化并发(Structured Concurrency)的核心思想:所有的控制流必须有清晰的入口和出口,且所有协程必须在退出前完成。
为了更好地管理协程生命周期,Kotlin 提供了两个关键工具:launch {}
和 async {}
。我们下面来详细看看它们的使用方式和区别。
2. 使用 launch {}
启动协程(作为 Job)
启动协程最基础的方式是使用 launch {}
。要使用它,我们需要先创建一个 CoroutineScope
,然后在其作用域内调用 launch {}
:
val job: Job = launch {
println("I am executed, but you only see the side-effects")
}
✅ launch {}
返回一个 Job
对象,我们可以调用 join()
等待其完成,或者在协程抛出异常时捕获异常。
✅ 我们还可以通过 start
参数延迟协程的执行。例如:
CoroutineStart.LAZY
:只有调用job.join()
时才会执行CoroutineStart.DEFAULT
(默认):立即执行CoroutineStart.ATOMIC
:防止协程在第一个挂起点前被取消CoroutineStart.UNDISPATCHED
:在当前线程执行直到第一个挂起点,之后根据上下文调度
如果我们需要处理多个不同生命周期的协程,可以使用如下方式:
val customRoutine = launch {
println("${Thread.currentThread().name}: I am launched in a class scope")
}
val globalRoutine = GlobalScope.launch {
println("${Thread.currentThread().name}: I am launched in a global scope")
}
⚠️ 注意:这种方式是“即发即弃”(fire-and-forget)的,不会阻塞调用线程,也不会返回任何结果。我们只能知道它是否成功或失败。
如果我们需要获取返回值,则应使用 async {}
。
3. 使用 async {}
获取协程结果
async {}
与 launch {}
类似,但它会立即返回一个 Deferred<T>
类型的对象,其中 T
是协程体返回的类型。我们可以通过调用 await()
方法来获取最终结果。
val futureResult: Deferred<String> = async {
"Hello, world!"
}
runBlocking {
println(futureResult.await())
}
✅ async {}
也非常适合在同一个协程作用域中实现并发操作。
✅ 与 launch
一样,我们也可以通过 start
参数控制 async
的执行时机和线程行为:
val first = async(start = CoroutineStart.LAZY) {
println("${Thread.currentThread().name}: I am lazy and launched only now")
"Hello, "
}
val second = async {
println("${Thread.currentThread().name}: I am eager!")
"world!"
}
runBlocking {
println(first.await() + second.await())
}
⚠️ 踩坑提醒:上面这段代码中,由于 first
是懒加载的,second
的输出会先于 first
,这可能会导致顺序不符合预期,使用时需注意。
4. 总结
本文我们对比了 launch {}
和 async {}
的使用方式和区别:
特性 | launch {} |
async {} |
---|---|---|
返回类型 | Job |
Deferred<T> |
是否返回结果 | ❌ 只能产生副作用 | ✅ 支持返回值 |
是否适合并发 | ✅ 适合 | ✅ 更适合 |
是否支持懒加载 | ✅ 支持 | ✅ 支持 |
典型用途 | 启动无返回值任务 | 启动需要返回值的任务 |
简而言之,如果你只需要启动一个任务并关注其副作用,使用 launch {}
;如果你需要等待协程返回一个结果,使用 async {}
。
示例代码已上传至 GitHub:Kotlin Coroutines 示例代码