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 示例代码


原始标题:Launching Kotlin Coroutines: With a Result or a Side Effect