1. Kotlin 协程简介
Kotlin 协程(Coroutines)是 Kotlin 提供的一套用于简化异步编程的轻量级线程机制。与传统的回调或 RxJava 等响应式编程方式相比,协程以同步风格编写异步代码,大大提升了代码的可读性和可维护性。
协程的执行依赖于 CoroutineContext,它由多个元素组成,常见的包括:
CoroutineDispatcher
:决定协程在哪个线程上运行Job
:用于管理协程的生命周期,如启动、取消等CoroutineName
:协程名称,用于调试CoroutineId
:协程唯一标识
协程通常在某个 CoroutineScope 中启动,这个 scope 提供了结构化并发的能力,确保父子协程之间的生命周期一致性。
现在我们重点来看两个常用的协程操作:async/await
和 withContext()
。
2. 什么是 async/await?
async
是 CoroutineScope
的扩展函数,用于启动一个新的协程,并返回一个 Deferred<T>
对象。我们可以调用 await()
来等待其结果。
val result = async { doSomething() }
val value = result.await()
✅ 优点:
- 支持并发执行多个任务
- 可以捕获异常并处理
❌ 注意点:
- 如果
async
中抛出异常且未被await()
捕获,会导致整个父协程取消 - 默认情况下,
async
是立即执行的,但也可以通过CoroutineStart.LAZY
延迟启动
示例:并发执行多个任务
val time = measureTimeMillis {
val task1 = async { doTheTask(DELAY) }
val task2 = async { doTheTask(DELAY) }
task1.await()
task2.await()
}
Assertions.assertTrue(time < DELAY * 2)
3. 什么是 withContext?
withContext(context)
是一个作用域函数,它会切换协程的上下文(如线程池),并在执行完代码块后自动恢复到原来的上下文。
val result = withContext(Dispatchers.IO) {
doNetworkCall()
}
✅ 优点:
- 更简洁,不需要手动调用
await
- 内部异常会自动传播,不需要额外处理
❌ 注意点:
withContext
是串行执行的,多个withContext
调用会按顺序执行- 不适合用于并行任务
示例:串行执行多个任务
val time = measureTimeMillis {
val dispatcher = newFixedThreadPoolContext(2, "withc")
withContext(dispatcher) {
doTheTask(DELAY)
}
withContext(dispatcher) {
doTheTask(DELAY)
}
}
Assertions.assertTrue(time >= DELAY * 2)
4. async-await 与 withContext 对比
对比项 | async-await | withContext |
---|---|---|
是否返回结果 | 是,通过 await() |
是,直接返回 |
是否支持并发 | ✅ 支持 | ❌ 不支持 |
是否需要手动 await | ✅ 需要 | ❌ 不需要 |
是否切换上下文 | ✅ 可以 | ✅ 可以 |
是否结构化并发 | ✅ 是 | ✅ 是 |
是否自动传播异常 | ❌ 需手动处理 | ✅ 是 |
是否适合单一任务 | ⚠️ 不推荐 | ✅ 推荐 |
是否适合并行任务 | ✅ 推荐 | ❌ 不推荐 |
5. 使用建议与总结
✅ 推荐使用场景
- withContext:当你只需要执行一个任务并返回结果时,比如网络请求、数据库查询等,简洁又安全。
- async/await:当你需要并发执行多个互不依赖的任务时,比如并行下载多个文件、并行处理多个数据源。
⚠️ 踩坑提醒
async
中抛出的异常必须在await()
时捕获,否则会触发父协程取消,造成“级联取消”。- 多个
withContext
是串行执行的,不要误以为它能并发执行。 - 避免在
async
中使用runBlocking
,容易造成死锁。
✅ 性能建议
withContext
的性能略优于async + await
,因为它内部做了优化,没有创建Deferred
的额外开销。- 如果只是切换线程执行一个任务并获取结果,优先使用
withContext
。
6. 示例源码
所有完整示例代码可在 GitHub 获取:
总结:withContext
更适合单一任务的上下文切换,而 async/await
更适合并发任务的组合。理解它们的使用场景和区别,能帮助你在实际开发中写出更清晰、更高效的协程代码。