协程async vs launch 的异常与结果学

1. 核心差异一览

维度 launch async
返回类型 Job(无结果) Deferred(Job 的子类型,有结果)
获取结果 无;只能 join() 等它结束 await() 拿 T(或抛出异常)
异常抛出的时机 立即 向上冒泡: - 若是根协程 :交给 CoroutineExceptionHandler(或默认报错); - 若在 coroutineScope:立刻取消父作用域,父作用域最终抛出该异常 延迟到消费结果时 :异常被封存在 Deferred ,在 await() 时抛出;但→ 在结构化并发里同样会立刻取消父作用域(见 §2)
CoroutineExceptionHandler 能处理 launch 的未捕获异常(尤其根协程) 对 async 无效(异常被当作结果的一部分保存,只有 await 才抛)
只等待不取结果 job.join() 仅等待完成;发生失败通常表现为 CancellationException(原因是实际异常) deferred.join() 也只等待;不会抛原始异常,拿原始异常要用 await() 或 getCompletionExceptionOrNull()

口诀:要结果用 async/await;要"开火忘返"用 launch。
launch 的异常"见光死"(马上上抛);async 的异常"装进盒子"(await 再打开),但依然会取消父作用域


2. 结构化并发中的异常传播

2.1coroutineScope {}(默认父子关联)

  • 子协程(不论 launch 还是 async)一旦失败

    1. 立即取消 父作用域与其兄弟
    2. 父作用域最终抛出首个异常 (其余作为 suppressed 附着)。
  • 对 async:即使你没有 await ,它失败后也会立刻把整个 coroutineScope 推向失败(常见现象:后续 delay/withContext 被取消)。

kotlin 复制代码
suspend fun demo() = coroutineScope {
    val d = async { error("boom") }
    // 这里就会被取消(不等你 await)
    delay(1000) // ← 立刻取消
    // 退出 scope 时抛 "boom"
}

2.2

supervisorScope {}/SupervisorJob

  • 子失败不会取消兄弟和父;
  • 对 async:异常仍封在 Deferred 里,只有 await 才会抛 ;如果你从不 await,异常会"沉没在 Deferred 里"(但 deferred.isCompleted 为真,getCompletionExceptionOrNull() 可见)。
css 复制代码
supervisorScope {
    val a = async { error("A") }   // 不会波及 b
    val b = async { 42 }
    // ... 此处仍可继续
    // 想感知 A 的失败:a.await() / a.getCompletionExceptionOrNull()
}

2.3 根协程(无父 Job)

  • GlobalScope.launch { ... } / viewModelScope.launch { ... }(顶层):未捕获异常交给 CoroutineExceptionHandler 或默认处理(日志/崩溃)。
  • GlobalScope.async { ... }:没有 await() 就没有异常报告 (静默失败)。因此禁止"裸 GlobalScope.async 不 await"。

3.

join/await/awaitAll/joinAll行为

  • job.join():只等结束;若失败,多数场景得到 CancellationException(其 cause 才是根因)。

  • deferred.await():

    • 成功 → 返回 T
    • 失败 → 直接抛出原始异常(不是 CE 包装)。
  • awaitAll(d1, d2, ...):

    • 任一失败 → 立刻 取消其他 Deferred 并抛出首个异常(其余压到 suppressed)。
  • joinAll:只等待全部完成;不会把原始异常抛给你。

想"并发+失败尽早"的结果收集 → 用 awaitAll;
想"并发但不因一个失败中断汇总",在 async 内部 try/catch 包装成成功/失败的 Result,再统一处理。


4. 推荐用法(套路)

4.1 并发取结果(标准答案)

kotlin 复制代码
suspend fun load(): Pair<User, Feed> = coroutineScope {
    val u = async { api.user() }
    val f = async { api.feed() }
    // 任何一个失败都会取消另一个并抛异常
    u.await() to f.await()
}

4.2 半容错并发(不想"一死全死")

kotlin 复制代码
suspend fun loadTolerant(): Pair<Result<User>, Result<Feed>> = supervisorScope {
    val u = async { runCatching { api.user() } }
    val f = async { runCatching { api.feed() } }
    u.await() to f.await()   // 不会互相取消,统一返回 Result
}

4.3

launch捕获与上报

scss 复制代码
val handler = CoroutineExceptionHandler { _, e -> log(e) }

scope.launch(handler) {
    try {
        work()
    } catch (e: Known) {
        handle(e)    // 业务内消化
    }               // 其他未捕获异常 → 走 handler
}

4.4 观察

async的失败(不用await也要感知)

scss 复制代码
val d = async { job() }
d.invokeOnCompletion { e ->
    if (e != null) log("async failed", e)  // 结构化或监督场景下的监控
}

5. 常见坑

  1. GlobalScope.async { ... } 不 await:异常静默;CoroutineExceptionHandler 也收不到。
  2. 只 join 不 await 以为能拿到异常:join 拿不到原始异常,最多一个 CancellationException。
  3. 在 coroutineScope 里 async 失败还以为不会影响别人:会的!除非用 supervisorScope。
  4. 把 CoroutineExceptionHandler 用在 async:没用,await 才会抛;要么 try/catch await,要么 invokeOnCompletion/getCompletionExceptionOrNull()。
  5. 忘记取消未使用的 Deferred:如果业务分支不再需要结果,记得 deferred.cancel(),减少无谓计算与资源占用。

6. 速记清单

  • 要结果 → async/await;只做事→ launch。
  • launch 异常立即 冒泡;async 异常在 await 抛 ,但仍会取消父作用域(非监督)。
  • 需要"彼此独立"→ supervisorScope 或 SupervisorJob。
  • 汇总并发结果:awaitAll(失败即抛);容错:Result + supervisorScope。
  • 根协程:launch 异常走 CoroutineExceptionHandler;async 必须 await 才能看到异常。
相关推荐
找方案13 小时前
AI+人力资源:AI招聘面试的兴起与争议
人工智能·面试·职场和发展
CC数分20 小时前
2026年HRBP岗位硬性要求和加分项
面试·职场和发展·数据分析
2601_9626802220 小时前
数据分析面试常考的SQL题和业务题分别是哪几类?
sql·面试·数据分析
黄敬峰20 小时前
一文搞懂 Agentic RAG:用 LangGraph 打造会思考的智能检索架构
面试
晚安日记wanna21 小时前
SSR 为什么不适合登录态从水合冲突到缓存串号
前端·react.js·面试
晚安日记wanna21 小时前
压测 TPS 卡在 800CPU 只跑四成六步定位法
运维·面试·测试
晚安日记wanna21 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
晚安日记wanna21 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
晚安日记wanna21 小时前
大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务
数据库·面试·架构
晚安日记wanna21 小时前
MySQL 主从延迟别只答并行复制
数据库·面试·架构