当协程的某个async失败之后,剩余的其它的会执行完还是会立刻取消?

31 阅读2分钟

短答:默认会立刻取消

在同一个(非监督的)协程作用域里,任意一个 async 失败(抛出未捕获异常)会取消父作用域,其兄弟协程随即被取消;它们会在下一个可取消挂起点(如 delay/IO/emit/send)收到 CancellationException 并结束。


三种常见语义 & 正确写法

1) 默认语义:一失败,全部取消(fail fast)

suspend fun failFast() = coroutineScope {
  val a = async { workA() }
  val b = async { workB() }          // 若这里抛异常
  val c = async { workC() }          // ← 会被取消
  awaitAll(a, b, c)                  // 抛出 b 的异常
}
  • 适合要么都成功,要么全失败的事务式场景。

  • 注意:async 的异常存储在 Deferred 里并在 await/awaitAll 时抛,但取消信号会立刻向兄弟传播

2) 想让其他任务继续完成:用supervisorScope(监督作用域)

suspend fun keepOthersRunning() = supervisorScope {
  val a = async { workA() }
  val b = async { workB() }          // 失败不会取消 a/c
  val c = async { workC() }

  // 别用 awaitAll(a,b,c) 直接把异常抛出作用域(会导致取消);分别处理:
  val ra = runCatching { a.await() }
  val rb = runCatching { b.await() } // 失败被吃掉
  val rc = runCatching { c.await() }
  listOf(ra, rb, rc).mapNotNull { it.getOrNull() }
}
  • 监督作用域下,子协程失败不会取消兄弟;但若你把异常向外抛出 supervisorScope,它也会取消所有子协程。所以在作用域内部消化异常

3) 想“收集成功、忽略失败”:结果即语义

suspend fun collectSuccesses() = supervisorScope {
  val jobs = tasks.map { t -> async { runCatching { doWork(t) } } }
  jobs.awaitAll().mapNotNull { it.getOrNull() }   // 成功的留下
}

细节补充

  • 取消是协作式:CPU 密集代码若没有挂起点或 yield()/isActive 检查,响应取消会滞后。
  • launch vs async:两者子协程失败都会取消父作用域;区别在于异常传播方式(launch 立即向上报;async 在 await 时抛)。
  • 想手动策略:拿到 Deferred 列表后,你也可以在捕获到某个失败时显式 others.forEach { it.cancel() } 或相反地继续 await 其他。

速记

  • 默认(coroutineScope) :一挂全挂(fail fast)。
  • 监督(supervisorScope/SupervisorJob) :彼此独立,异常要在作用域里消化才能让其他继续。
  • 响应取消取决于挂起点:加 delay/IO/yield/isActive 让任务“能被停得下来”。