Kotlin 协程(三)协程的常用关键字使用及其比较

发布于:2025-03-05 ⋅ 阅读:(291) ⋅ 点赞:(0)

在使用协程时,经常会用到suspend、launch、async、await、withContext、runBlocking这些关键字,这儿对其进行解释和比较

为了更好地理解 suspend、launch、async、await、withContext、runBlocking 之间的区别,我们可以从 挂起、协程启动、作用域管理 等方面进行对比。

1. suspend 关键字(挂起与恢复)

  • suspend 关键字用于 定义挂起函数,表示该函数可以在非阻塞的情况下暂停(挂起)并恢复。
  • 挂起函数只能在 协程或其他挂起函数 中调用。
  • 挂起时不会阻塞线程,而是让出线程,允许其他协程执行。

2. launch(启动一个新的协程,返回 Job)

  • launch 创建 并启动一个新的协程 但不会返回结果。
  • 适用于 不需要返回值,例如更新 UI、写日志等。
  • 返回一个 Job,可以用 job.cancel() 取消该协程。

3. async & await(并发执行任务,返回 Deferred<T>)

  • async 启动 并发任务 并返回一个 Deferred<T>(类似于 Future)。
  • await() 用于获取 async 返回的结果,如果任务未完成,它会挂起协程直到完成。
suspend fun loadData(): String {
    return withContext(Dispatchers.IO) {
        delay(1000)
        "Data loaded"
    }
}

suspend fun testAsync() {
    val deferred1 = async { loadData() }
    val deferred2 = async { loadData() }

    // await() 取出结果
    val result1 = deferred1.await()
    val result2 = deferred2.await()
    
    println("Result: $result1, $result2")
}
  • 需要 并行执行多个任务 并等待结果:
    • 多个网络请求
    • 批量数据库查询
    • 计算密集型任务

async 类似于 launch,但它返回结果,launch 只是执行,不返回值。

4. withContext(切换协程上下文,执行完成后返回结果)

  • withContext 切换 到指定的调度器(线程池),执行代码块,并返回结果。
  • 等待代码块执行完成后再继续,不会创建新的协程,而是挂起当前协程并切换线程。
suspend fun fetchData(): String {
    return withContext(Dispatchers.IO) {  // 切换到 IO 线程
        delay(1000)
        "Fetched Data"
    }
}
  • 切换线程池,适用于:
    • 网络请求(Dispatchers.IO)
    • 数据库查询(Dispatchers.IO)
    • 计算密集型任务(Dispatchers.Default)

withContext 不会并发执行,它只是切换线程,等任务完成后再返回。

Dispatchers 预定义调度器

调度器 作用 适用场景
Dispatchers.Default 适用于 CPU 密集型任务(计算、加密等) 计算任务,如排序、数学运算等
Dispatchers.IO 适用于 I/O 操作(磁盘、网络、数据库) 读写文件、数据库查询、网络请求
Dispatchers.Main 适用于 Android 主线程 更新 UI,处理用户交互
Dispatchers.Unconfined 继承调用方线程,恢复时可能切换线程 测试或临时任务,不建议使用
newSingleThreadContext("MyThread") 自定义单线程调度器 特定线程执行任务

5. launch vs async vs withContext 对比

关键字 是否返回结果 适用场景 线程调度
launch ❌ 不返回结果 执行任务但不关心结果 不切换线程
async ✅ 返回 Deferred<T> 并发执行多个任务并等待结果 不切换线程
withContext ✅ 返回结果 切换线程并执行任务 切换线程

6.runBlocking 详解

runBlocking 是 Kotlin 协程中的一个函数,它 阻塞当前线程 并运行一个新的协程,直到该协程及其子协程执行完毕后才会继续执行后续代码。一般不会用到,只要用于测试

fun main() {
    runBlocking {
        println("协程开始")
        delay(1000)
        println("协程结束")
    }
    println("主线程继续执行")
}

执行流程

  1. runBlocking 启动 一个协程 并阻塞当前线程(如 main 线程)。
  2. delay(1000) 挂起协程,但 runBlocking 仍然阻塞 线程,其他代码不会执行。
  3. 协程执行完 delay(1000) 之后,继续执行 println("协程结束")。
  4. runBlocking 结束后,主线程才会继续执行 println("主线程继续执行")。
fun main() {
    runBlocking {
        launch {
            delay(1000)
            println("子协程完成")
        }
        println("runBlocking 结束")
    }
    println("主线程继续")
}

执行流程

  1. runBlocking 启动 一个主协程,阻塞 main 线程。
  2. launch 创建一个子协程,但 launch 不会阻塞 runBlocking,它只是异步执行。
  3. println("runBlocking 结束") 立即执行,不等待 launch 内部的 delay(1000) 。
  4. runBlocking 结束后,main 线程继续执行 println("主线程继续")。
  5. launch 子协程在后台继续运行,1 秒后打印 "子协程完成"。

7.启动新协程

关键字 作用 返回值 是否阻塞线程 适用于
launch 启动一个新的协程,异步执行代码 Job ❌ 否 适合不需要返回值的任务
async 启动一个新的协程,返回 Deferred Deferred<T> ❌ 否 适合需要返回值的任务

8. 作用域管理

关键字 作用 是否阻塞线程 适用于
runBlocking 阻塞当前线程,启动协程 ✅ 是 main 函数、单元测试
coroutineScope 创建新的协程作用域,等待所有子协程完成 ❌ 否 挂起函数内部管理协程
withContext 在指定调度器中切换上下文并执行代码 ❌ 否 线程切换(如 IO 线程)

总结

关键字 作用 是否阻塞线程 是否创建新协程 适用于
suspend 让函数支持挂起 ❌ 否 ❌ 否 定义可挂起函数
launch 启动一个协程,无返回值 ❌ 否 ✅ 是 异步执行无返回值任务
async 启动一个协程,返回 Deferred<T> ❌ 否 ✅ 是 计算任务,需要返回值
await 挂起当前协程,等待 async 结果 ❌ 否 ❌ 否 获取 async 结果
runBlocking 启动协程并阻塞线程 ✅ 是 ✅ 是 main 函数、测试
coroutineScope 创建作用域,等待所有协程 ❌ 否 ❌ 否 在挂起函数内部管理协程
withContext 切换协程执行的线程 ❌ 否 ❌ 否 线程切换,如 Dispatchers.IO


微信公众号

今日签到

点亮在社区的每一天
去签到