一句话总结
launch + join vs. async + await:Kotlin协程的任务协调哲学
launch + join→ “等一个没有结果的任务完成” (比如等后台任务结束)。async + await→ “等一个有结果的任务完成,并拿到返回值” (比如等网络请求返回数据)。
一、核心区别:目的与返回值
launch 和 async 都是协程构建器,但它们的根本区别在于目的和返回值。
launch() + join() | async() + await() | |
|---|---|---|
| 目的 | 等待任务完成。launch 启动一个协程,join 是一个挂起函数,它会暂停当前协程,直到 launch 协程完成。 | 等待任务完成并获取结果。async 启动一个协程并返回一个 Deferred<T> 对象,await 是一个挂起函数,它会暂停当前协程,直到 Deferred 任务完成并返回结果。 |
| 返回值 | launch 返回 Job,它是一个无返回值的句柄,用于管理协程的生命周期和状态。 | async 返回 Deferred<T> ,它继承自 Job,并额外提供了一个 await() 方法来获取结果。 |
| 典型场景 | 确保任务按顺序执行,或在继续之前等待一个后台操作完成。 | 并发执行多个任务,并需要它们的返回值进行进一步处理。 |
二、实践对比:顺序与并发
-
launch + join:顺序执行
当需要确保任务 A 在任务 B 之前完成时,join 是一个简单有效的同步工具。
// 任务A和任务B需要顺序执行 suspend fun sequentialTasks() { val jobA = launch { taskA() } jobA.join() // 挂起当前协程,直到 taskA() 完成 val jobB = launch { taskB() } jobB.join() // 等待 taskB() 完成 } -
async + await:并发执行
当多个任务可以同时执行,并且你需要它们的返回值时,async 是实现高效并发的最佳选择。await 只会挂起当前协程,而不是阻塞底层线程。
// 同时执行 taskA() 和 taskB(),并合并结果 suspend fun concurrentTasks() { val deferredA = async { taskA() } val deferredB = async { taskB() } // 挂起当前协程,直到两个任务都完成 val resultA = deferredA.await() val resultB = deferredB.await() // 合并结果 combineResults(resultA, resultB) }
三、异常处理的陷阱
launch 和 async 在异常传播方面存在关键差异,这使得它们在错误处理方面各有优劣。
-
launch 的即时传播:
launch 协程中抛出的未捕获异常会立即传播给父协程。如果父协程没有处理,它会取消父协程及其所有子协程。这使得错误能被快速发现,但可能导致“全盘皆输”。
-
async 的延迟处理:
async 协程中抛出的异常会被封装在 Deferred 对象中,并暂存起来。只有在调用 await() 方法时,这个异常才会被抛出。这提供了更灵活的异常处理时机,但如果 await() 从未被调用,异常将永远不会被抛出,从而导致难以调试的**“异常静默”**。
四、结论:如何选择?
- 选择
launch + join:当你需要启动一个后台任务,并且在不关心其返回值的情况下,确保它在你的主流程继续之前完成时。 - 选择
async + await:当你需要并发执行多个任务,并且需要它们的返回值来进行进一步处理时。这是 Kotlin 协程实现并发编程的黄金搭档。
记住:能并发就别顺序。在大多数场景下,async + await 提供了更高的效率和更好的用户体验。