
这是对《理解kotlin协程》基础文章的深度补充。专门解决学习协程时最容易产生误解的4个核心问题。
很多开发者以为使用了async就能实现并行执行,但实际上启动时机决定了是否真正并行。这个误解可能导致3倍的性能损失。
问题的本质:很多人不理解async启动协程和await获取结果是两个完全独立的操作。
// ❌ 错误:看似用了async,实际是顺序执行
suspend fun loadDataWrong(): UserInfo {
val user = async { fetchUser() }.await() // 启动->立即等待
val profile = async { fetchProfile() }.await() // 上一个完成后才启动
val settings = async { fetchSettings() }.await() // 上一个完成后才启动
// 总时间:3秒(每个任务1秒)
return UserInfo(user, profile, settings)
}
// ✅ 正确:真正的并行执行
suspend fun loadDataCorrect(): UserInfo {
// 关键:先启动所有任务
val userTask = async { fetchUser() } // 立即启动
val profileTask = async { fetchProfile() } // 立即启动
val settingsTask = async { fetchSettings() } // 立即启动
// 此时三个任务都在并行执行
// 然后等待所有结果
val user = userTask.await() // 等待第一个结果
val profile = profileTask.await() // 等待第二个结果
val settings = settingsTask.await() // 等待第三个结果
// 总时间:1秒(最慢任务的时间)
return UserInfo(user, profile, settings)
}性能对比:
另一个常见疑问:调用await会阻塞后续代码吗?
suspend fun demonstrateAwaitBehavior() {
println("1. 开始")
val task = async {
delay(2000)
"任务完成"
}
println("2. async任务已启动")
val result = task.await() // 在协程内部,这里会"等待"结果
println("3. 拿到结果: $result") // 必须等await完成才执行
}关键理解:
await确实会让当前协程"停住"等待结果对于习惯了JavaScript Promise的开发者:
// JavaScript: Promise.all实现并行
const [user, profile, settings] = await Promise.all([
fetchUser(),
fetchProfile(),
fetchSettings()
]);// Kotlin: async/await实现并行
val userTask = async { fetchUser() }
val profileTask = async { fetchProfile() }
val settingsTask = async { fetchSettings() }
val user = userTask.await()
val profile = profileTask.await()
val settings = settingsTask.await()两者的核心思想相同:先启动所有异步任务,再等待所有结果。
这是前端开发者学习协程时的最大困惑点。让我们先理解根本差异:
// JavaScript: 单线程 + 事件循环
console.log("1. 同步代码");
setTimeout(() => console.log("3. 异步任务"), 0);
Promise.resolve().then(() => console.log("2. 微任务"));
console.log("1. 同步代码结束");
// 所有代码都在一个线程上执行,没有选择的余地// Kotlin: 多线程 + 协程调度
println("1. 同步代码")
launch(Dispatchers.IO) { // 可以选择在IO线程池执行
println("在IO线程: ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // 可以选择在CPU线程池执行
println("在CPU线程: ${Thread.currentThread().name}")
}
println("1. 同步代码结束")核心差异:JavaScript只有一个执行线程,Kotlin可以选择在不同的线程池中执行协程。
把调度器想象成餐厅的不同部门:
// Dispatchers.IO - 后厨部门(IO密集型任务)
launch(Dispatchers.IO) {
val data = fetchFromAPI() // 网络请求
val file = readFromDisk() // 文件读写
val dbResult = queryDatabase() // 数据库查询
}
// Dispatchers.Default - 配菜部门(CPU密集型任务)
launch(Dispatchers.Default) {
val processed = processLargeDataSet() // 数据处理
val calculated = complexCalculation() // 复杂计算
val sorted = sortMillionItems() // 排序算法
}
// Dispatchers.Main - 服务部门(UI相关,Android特有)
launch(Dispatchers.Main) {
updateProgressBar() // 更新进度条
showSuccessMessage() // 显示成功消息
refreshUserInterface() // 刷新界面
}class UserRepository {
// 网络请求:用IO调度器
suspend fun fetchUserFromAPI(userId: Int): User = withContext(Dispatchers.IO) {
apiService.getUser(userId)
}
// 数据处理:用Default调度器
suspend fun processUserData(rawData: String): ProcessedData = withContext(Dispatchers.Default) {
// 复杂的数据处理逻辑
parseAndValidateUserData(rawData)
}
// 综合使用:在不同调度器间切换
suspend fun loadAndProcessUser(userId: Int): ProcessedUser {
// 1. 在IO线程获取数据
val rawUser = withContext(Dispatchers.IO) {
apiService.getUser(userId)
}
// 2. 在CPU线程处理数据
val processedData = withContext(Dispatchers.Default) {
heavyDataProcessing(rawUser)
}
// 3. 返回处理后的数据
return ProcessedUser(processedData)
}
}选择原则:
Dispatchers.IODispatchers.Default Dispatchers.Main(Android项目)理解协程的关键是区分这三种模式:
fun demonstrateThreeModes() {
// 1. 阻塞模式:runBlocking
println("runBlocking之前")
runBlocking {
delay(1000) // 线程在这里被占用,无法做其他事
}
println("runBlocking之后") // 必须等上面完成才能执行
// 2. 非阻塞模式:launch
println("launch之前")
GlobalScope.launch {
delay(1000) // 协程在后台执行,主线程继续
}
println("launch之后") // 立即执行,不等待launch完成
// 3. 挂起模式:只能在协程内演示
runBlocking {
println("挂起函数之前")
delay(1000) // 当前协程挂起,线程可以执行其他协程
println("挂起函数之后") // 协程恢复后才执行
}
}挂起 = 协程内部的"阻塞" + 只能在协程内执行
// ✅ 在协程内部,挂起函数表现得像同步调用
suspend fun businessLogic() {
println("开始处理")
val userData = fetchUser() // 挂起等待用户数据
val profileData = fetchProfile() // 挂起等待资料数据
val result = processData(userData, profileData) // 处理数据
println("处理完成: $result")
}
// ❌ 普通函数无法调用挂起函数
fun ordinaryFunction() {
// fetchUser() // 编译错误!挂起函数只能在协程内调用
}为什么这样设计?
挂起函数只能在两个地方调用:
// ✅ 场景1:挂起函数调用挂起函数
suspend fun serviceA() {
val data = serviceB() // serviceB也是挂起函数
}
// ✅ 场景2:协程内调用挂起函数
fun startWork() {
launch {
val result = serviceA() // 在协程内调用挂起函数
}
}让我们通过实际场景来理解什么时候用哪个构建器:
class UserService {
// ✅ launch: 执行后台任务,不需要返回值
fun sendAnalytics(event: String) {
serviceScope.launch {
analyticsAPI.send(event) // 发送就忘记,不关心结果
}
}
// ✅ async: 需要返回值的并行任务
suspend fun loadUserDashboard(userId: Int): Dashboard {
val userTask = async { fetchUser(userId) }
val postsTask = async { fetchUserPosts(userId) }
val statsTask = async { fetchUserStats(userId) }
return Dashboard(
user = userTask.await(),
posts = postsTask.await(),
stats = statsTask.await()
)
}
// ✅ runBlocking: 桥接普通代码和协程(主要用于测试和main函数)
@Test
fun testUserService() = runBlocking {
val user = userService.fetchUser(123)
assertEquals("expected", user.name)
}
}// ❌ 错误:在业务代码中使用runBlocking
class BadUserController {
fun getUser(id: Int): User {
return runBlocking { // 阻塞线程,影响性能
userService.fetchUser(id)
}
}
}
// ✅ 正确:让控制器支持挂起函数
class GoodUserController {
suspend fun getUser(id: Int): User {
return userService.fetchUser(id) // 不阻塞线程
}
}// ❌ 错误:单个任务使用async
suspend fun processUser(user: User) {
val result = async { validateUser(user) }.await() // 没必要的包装
updateDatabase(result)
}
// ✅ 正确:直接调用挂起函数
suspend fun processUser(user: User) {
val result = validateUser(user) // 简单直接
updateDatabase(result)
}// ❌ 错误:没有异常处理
fun loadData() {
launch {
val data = riskyNetworkCall() // 可能抛出异常
updateUI(data)
}
}
// ✅ 正确:适当的异常处理
fun loadData() {
launch {
try {
val data = riskyNetworkCall()
updateUI(data)
} catch (e: Exception) {
showErrorMessage(e.message)
}
}
}需要返回值?
├─ 是 → 用 async + await
└─ 否 → 需要等待完成?
├─ 是 → 在普通函数中?
│ ├─ 是 → 用 runBlocking(仅限测试/main)
│ └─ 否 → 直接调用挂起函数
└─ 否 → 用 launch通过这篇深入解析,我们解决了协程学习中的四个核心难点:
这些深入理解将帮助你从"会用协程"提升到"精通协程",在实际项目中写出高效且正确的协程代码。
最重要的一点:协程的设计目标是让异步代码写起来像同步代码一样简单,但这种简单性建立在对底层机制的深入理解之上。掌握了这些核心概念,你就能充分发挥协程的威力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。