Kotlin 协程之launch、async、suspend 函数和调度器(Dispatchers)

在 Kotlin 协程中,launchasyncsuspend 函数和调度器(Dispatchers)是核心组件,它们共同实现了轻量级的并发编程。以下是详细解析和使用示例:

一、协程基础概念

协程是一种轻量级线程,由 Kotlin 运行时管理(而非操作系统),可以在单线程内实现多任务切换,避免线程上下文切换的开销。

二、suspend 函数:协程的 "暂停点"

suspend 是用于修饰函数的关键字,标记该函数只能在协程或其他 suspend 函数中调用,且内部可以 "暂停" 执行(不会阻塞线程)。

  • 特性
    • 暂停时会保存当前状态(局部变量、执行位置等),恢复时继续执行。
    • 本身不会创建协程,仅标记可暂停的操作(如网络请求、IO 等)。

kotlin

复制代码
// 定义 suspend 函数(模拟耗时操作)
suspend fun fetchData(): String {
    delay(1000) // delay 是 Kotlin 提供的 suspend 函数,模拟暂停(非阻塞)
    return "Data from network"
}
  • 注意suspend 函数不能直接在主线程(非协程)中调用,必须通过协程构建器启动。

三、协程构建器:launchasync

用于创建并启动协程,最常用的是 launchasync

1. launch:启动 "无返回值" 的协程
  • 作用:启动一个协程,返回 Job 对象(用于管理协程生命周期,如取消、等待)。
  • 适用场景:执行不需要返回结果的后台任务(如日志打印、数据保存)。

kotlin

复制代码
import kotlinx.coroutines.*

fun main() = runBlocking { // runBlocking 是顶层函数,用于在非协程环境启动协程(会阻塞当前线程直到内部协程完成)
    // 启动一个协程
    val job = launch {
        println("Coroutine started")
        delay(1000) // 暂停 1 秒(非阻塞)
        println("Coroutine finished")
    }
    
    job.join() // 等待协程执行完成(阻塞当前协程,非线程)
    println("Main finished")
}
  • 输出: plaintext

    复制代码
    Coroutine started
    Coroutine finished
    Main finished
2. async:启动 "有返回值" 的协程
  • 作用:启动一个协程,返回 Deferred<T> 对象(Job 的子类),通过 await() 获取返回值(T 类型)。
  • 适用场景:需要获取结果的异步任务(如并行请求多个接口)。

kotlin

复制代码
fun main() = runBlocking {
    // 启动带返回值的协程
    val deferred = async {
        delay(1000)
        42 // 返回值
    }
    
    val result = deferred.await() // 等待结果(非阻塞,会暂停当前协程)
    println("Result: $result") // 输出:Result: 42
}
  • 并行执行async 适合并行任务,多个 async 可同时执行,最后用 await() 汇总结果:

kotlin

复制代码
fun main() = runBlocking {
    val time = measureTimeMillis { // 测量执行时间
        val deferred1 = async { fetchData1() }
        val deferred2 = async { fetchData2() }
        // 并行执行,总时间约为较慢任务的时间(而非两者之和)
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        println("Total: $result1 + $result2 = ${result1 + result2}")
    }
    println("Time: $time ms") // 约 2000 ms(因 fetchData2 耗时 2 秒)
}

suspend fun fetchData1(): Int {
    delay(1000)
    return 100
}

suspend fun fetchData2(): Int {
    delay(2000)
    return 200
}

四、调度器(Dispatchers):指定协程运行的线程

协程需要运行在调度器指定的线程池上,Kotlin 提供了几种内置调度器:

调度器 作用 适用场景
Dispatchers.Main 主线程(Android 中是 UI 线程) 更新 UI(需依赖 Android 库)
Dispatchers.IO IO 线程池(网络、文件读写等) 耗时 IO 操作
Dispatchers.Default CPU 密集型任务线程池(默认) 计算密集型任务(如数据处理)
Dispatchers.Unconfined 不指定线程(在当前线程执行,暂停后恢复到 resume 线程) 特殊场景(谨慎使用)
使用方式:通过 CoroutineContext 指定

协程构建器(launch/async)可通过参数指定调度器:

kotlin

复制代码
fun main() = runBlocking {
    // 1. IO 调度器(处理网络请求)
    launch(Dispatchers.IO) {
        println("IO thread: ${Thread.currentThread().name}") // 输出:IO thread: DefaultDispatcher-worker-1
        fetchData()
    }.join()

    // 2. Default 调度器(处理计算任务)
    val result = async(Dispatchers.Default) {
        println("Default thread: ${Thread.currentThread().name}") // 输出:Default thread: DefaultDispatcher-worker-2
        1 + 1
    }.await()
    println("Result: $result")
}

五、协程上下文(CoroutineContext

调度器是 CoroutineContext 的一部分,上下文还包括 Job(协程生命周期)、CoroutineName(协程名称,用于调试)等。可通过 + 组合多个上下文元素:

kotlin

复制代码
fun main() = runBlocking {
    launch(Dispatchers.IO + CoroutineName("NetworkCoroutine")) {
        println("Name: ${coroutineContext[CoroutineName]}") // 输出:Name: CoroutineName(NetworkCoroutine)
        fetchData()
    }.join()
}

六、关键区别总结

组件 作用 返回值 适用场景
suspend 标记可暂停的函数 无(函数自身有返回值) 定义耗时操作
launch 启动无返回值的协程 Job 后台任务(无结果)
async 启动有返回值的协程 Deferred<T> 需结果的异步任务
Dispatchers 指定协程运行的线程池 - 控制任务执行的线程环境

七、注意事项

  1. runBlocking 的使用:仅用于测试或 main 函数,会阻塞当前线程,生产环境(如 Android)中避免使用。
  2. await() 的位置asyncawait() 应在需要结果时调用,过早调用会导致串行执行(失去并行优势)。
  3. 取消协程 :通过 Job.cancel() 取消协程,需确保 suspend 函数能响应取消(如 delay 是可取消的)。
  4. Android 中的主线程Dispatchers.Main 需要依赖 androidx.lifecycle:lifecycle-viewmodel-ktx 等库。

通过合理组合这些组件,可以高效实现异步、非阻塞的并发逻辑,避免传统线程模型的性能开销

相关推荐
Kapaseker4 小时前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish21 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z4 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton5 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream5 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam5 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc6 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite