1. Kotlin 协程简介

Kotlin 协程(Coroutines)是 Kotlin 提供的一套用于简化异步编程的轻量级线程机制。与传统的回调或 RxJava 等响应式编程方式相比,协程以同步风格编写异步代码,大大提升了代码的可读性和可维护性。

协程的执行依赖于 CoroutineContext,它由多个元素组成,常见的包括:

  • CoroutineDispatcher:决定协程在哪个线程上运行
  • Job:用于管理协程的生命周期,如启动、取消等
  • CoroutineName:协程名称,用于调试
  • CoroutineId:协程唯一标识

协程通常在某个 CoroutineScope 中启动,这个 scope 提供了结构化并发的能力,确保父子协程之间的生命周期一致性。

现在我们重点来看两个常用的协程操作:async/awaitwithContext()


2. 什么是 async/await?

asyncCoroutineScope 的扩展函数,用于启动一个新的协程,并返回一个 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 获取:

👉 GitHub 项目地址


总结withContext 更适合单一任务的上下文切换,而 async/await 更适合并发任务的组合。理解它们的使用场景和区别,能帮助你在实际开发中写出更清晰、更高效的协程代码。


原始标题:Kotlin withContext() vs. async-await