launch/join 与 async/await 在 Kotlin 中的对比:2025 年如何选择?

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

launch/join 与 async/await 在 Kotlin 中的对比:2025 年如何选择?完整解析与示例

\n\n

你好,同行!如果你曾经用 Kotlin 写过协程,那么你肯定问过自己这个问题:“launch 和 async 到底有什么区别?这个神秘的 join 又是干什么的?” 这个问题在 Stack Overflow 上可是个热门话题,获得了数千次浏览。这并非偶然:乍一看,这两个工具做的是同一件事——启动一个后台任务。但魔鬼,总是藏在细节里。

\n\n

在 2025 年,当 Kotlin 协程 已成为 Android 开发和后端开发的主流技术时,理解这些差异不仅仅是一种偏好,而是编写干净、安全且高性能代码的必要条件。在本文中,我们不会仅仅罗列干巴巴的事实。我们将分析真实场景,查看代码,并总结出你现在就可以在自己的项目中放心使用的最佳实践。

\n\n

开始吧!

\n\n

launch:“启动并忘记”

\n\n

launch 函数可能是启动协程最简单的方式。它创建一个 不返回结果 的新协程。它的任务是执行某些工作:更新 UI、发送日志、将数据写入文件。其返回类型是 Job。这就像给快递员一个任务:“把包裹送过去然后回来,不需要报告”。

\n\n

join 方法允许你等待该协程完成。它会挂起当前协程的执行,直到调用 join 的那个 job 完成。

\n\n

来看一个例子。假设我们需要并行加载两张图片,然后将它们合并。使用 launch 和 join 的方式如下:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    // 模拟加载过程\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithLaunch() = coroutineScope {\n    var image1: Image? = null\n    var image2: Image? = null\n\n    val job1 = launch {\n        image1 = loadImage("https://example.com/img1.png")\n    }\n\n    val job2 = launch {\n        image2 = loadImage("https://example.com/img2.png")\n    }\n\n    // 等待两个任务完成\n    job1.join()\n    job2.join()\n\n    // 现在 image1 和 image2 保证已加载完成\n    println("Images loaded: $image1 and $image2")\n}
\n\n

这里有什么重要的?我们使用了 可变变量(var)来保存结果。这不太优雅,并且可能导致线程问题,尽管协程是在单个线程内工作的。但主要的缺点是,我们不能直接使用 launch 来返回值。

\n\n

async:“启动并获取结果”

\n\n

而 async 则是“商务舱”。它创建一个 返回结果 的协程,结果以 Deferred<T> 对象的形式呈现。这就像叫一辆出租车:你等待,然后返回给你的不仅仅是行程本身,而是一个具体的结果——你到达了目的地。要获取这个结果,需要使用 await()。

\n\n

同样的图片加载示例,使用 async/await 会变得更加优雅:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithAsync() = coroutineScope {\n    val deferred1 = async { loadImage("https://example.com/img1.png") }\n    val deferred2 = async { loadImage("https://example.com/img2.png") }\n\n    // await() 会挂起协程,直到获取到结果\n    val image1 = deferred1.await()\n    val image2 = deferred2.await()\n\n    println("Images loaded: $image1 and $image2")\n}
\n\n

感受到区别了吗?没有 var,没有手动管理。一切都干净整洁,

博客

书籍推荐