Kotlin launch 与 async 的区别

401 阅读3分钟

一、功能定位与返回值差异

特性‌**launch**‌‌**async**‌
核心用途执行无需返回值的异步任务(如日志上传、I/O 操作)执行并发任务并汇总结果(如并行网络请求)
返回值类型Job(用于协程生命周期控制)Deferred<T>(延迟结果容器,需 await() 获取值)
典型场景后台任务执行,不依赖结果顺序性多任务并行执行后合并结果4

二、异常处理机制对比

  1. ‌**launch 的异常传播**‌
    子协程内未捕获的异常会立即传播到父协程,导致整个作用域取消(级联取消)。

    launch {  
        throw RuntimeException("异常!")  // ⚠️ 触发父协程取消  
    }  
    

    需通过 try-catch 手动捕获异常或使用 SupervisorJob 隔离异常。

  2. ‌**async 的异常延迟抛出**‌
    异常被暂存于 Deferred 对象中,只有调用 await() 时才会抛出异常45:

    val deferred = async { throw RuntimeException("埋伏的异常!") }  
    deferred.await()  // ⚠️ 此处才抛出异常  
    

    若未调用 await(),异常可能被静默忽略。


三、执行方式与代码示例

  1. ‌**launch:无返回值任务**‌

    // 启动两个独立任务,不关心执行顺序  
    val job1 = launch { uploadLogs() }  
    val job2 = launch { saveToLocal() }  
    // 可继续执行其他操作(如更新 UI)  
    
  2. ‌**async:并发任务与结果聚合**‌

    // 并行请求数据后合并  
    val dataA = async { fetchFromApiA() }  
    val dataB = async { fetchFromApiB() }  
    val combinedData = DataWrapper(dataA.await(), dataB.await())  
    

    使用 awaitAll() 可简化多任务等待:

    val results = awaitAll(async { task1() }, async { task2() })  
    
fun main() = runBlocking {

    //子协程
    val job = launch {
        println("coroutine launch start") // 2.进入子协程 按顺序打印此处
        delay(10000)
        println("coroutine launch end")// 4.deffered 挂起11s 进入子协程 打印此处
    }

    val deffered = async {
        println("coroutine async start")// 3.进入子协程 打印此处
        delay(11000)
        println("coroutine async end")//4. 进入子协程 打印此处
        "李元霸"
    }

    println("deffered 零 test derre")  //1.因前面两个子协程需要耗时 故而首先打印此处

    println("deffered 一:${deffered.await()}")//5.deffered 有返回,就打印此处
    delay(30000)
    println("deffered 二:${deffered.await()}")//6.deffered 有返回,就打印此处
}

四、关键注意事项

  1. 资源泄漏风险

    • launch 启动的任务若未绑定作用域生命周期,可能导致内存泄漏;
    • async 必须调用 await() 确保结果处理并暴露潜在异常。
  2. 线程调度策略

    • 两者均可指定调度器(如 Dispatchers.IO)优化线程使用;
    • async 默认继承父协程上下文,可通过参数覆盖14。
  3. 结构化并发原则
    推荐通过 coroutineScopesupervisorScope 管理并发任务,确保父作用域取消时子协程自动终止14:

    suspend fun fetchData() = coroutineScope {  
        val data = async { /* ... */ }  
        // 父作用域取消时自动取消 data 任务  
    }  
    

五、总结对比

维度‌**launch**‌‌**async**‌
目标执行即弃型任务获取并发任务结果
异常时机立即传播导致作用域取消延迟到 await() 抛出
线程切换依赖调度器配置launch
适用场景不依赖结果的独立任务(如埋点、I/O)并行任务结果聚合(如多接口请求合并)

选择原则‌:

  • 优先使用 launch 处理无需返回值的异步操作;
  • 需合并多个并发结果时选择 async + await()