launch/join 与 async/await 在 Kotlin 中的对比:2025 年如何选择?完整解析与示例
\n\n你好,同行!如果你曾经用 Kotlin 写过协程,那么你肯定问过自己这个问题:“launch 和 async 到底有什么区别?这个神秘的 join 又是干什么的?” 这个问题在 Stack Overflow 上可是个热门话题,获得了数千次浏览。这并非偶然:乍一看,这两个工具做的是同一件事——启动一个后台任务。但魔鬼,总是藏在细节里。
\n\n在 2025 年,当 Kotlin 协程 已成为 Android 开发和后端开发的主流技术时,理解这些差异不仅仅是一种偏好,而是编写干净、安全且高性能代码的必要条件。在本文中,我们不会仅仅罗列干巴巴的事实。我们将分析真实场景,查看代码,并总结出你现在就可以在自己的项目中放心使用的最佳实践。
\n\n开始吧!
\n\nlaunch:“启动并忘记”
\n\nlaunch 函数可能是启动协程最简单的方式。它创建一个 不返回结果 的新协程。它的任务是执行某些工作:更新 UI、发送日志、将数据写入文件。其返回类型是 Job。这就像给快递员一个任务:“把包裹送过去然后回来,不需要报告”。
join 方法允许你等待该协程完成。它会挂起当前协程的执行,直到调用 join 的那个 job 完成。
来看一个例子。假设我们需要并行加载两张图片,然后将它们合并。使用 launch 和 join 的方式如下:
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 来返回值。
async:“启动并获取结果”
\n\n而 async 则是“商务舱”。它创建一个 返回结果 的协程,结果以 Deferred<T> 对象的形式呈现。这就像叫一辆出租车:你等待,然后返回给你的不仅仅是行程本身,而是一个具体的结果——你到达了目的地。要获取这个结果,需要使用 await()。
同样的图片加载示例,使用 async/await 会变得更加优雅:
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,没有手动管理。一切都干净整洁,