本节讨论异常处理,以及由异常引发的取消。被取消的协程会在挂起点抛出 CancellationException,而协程机制会忽略这类异常。接下来关注两种情况:取消过程中抛出异常,以及同一协程的多个子协程同时抛出异常。
异常传播
协程构建器分为两类:自动传播异常的 launch,以及把异常交给使用者处理的 async 和 produce。
如果这些构建器创建的是根协程,即不是其他协程的子协程,第一类会把异常视为未捕获异常,类似 Java 的 Thread.uncaughtExceptionHandler;第二类则依赖使用者消费最终异常,例如调用 await 或 receive。produce 与 receive 的介绍见通道。
下面通过 GlobalScope 创建根协程,展示两种构建器的差别。
GlobalScope 是需要谨慎使用的 API,使用不当可能带来不容易察觉的问题。为整个应用创建一个根协程,是它少数合理用途之一;使用时必须通过
@OptIn(DelicateCoroutinesApi::class)明确选择接受该 API。
import kotlinx.coroutines.*
//sampleStart
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val job = GlobalScope.launch { // root coroutine with launch
println("Throwing exception from launch")
throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
}
job.join()
println("Joined failed job")
val deferred = GlobalScope.async { // root coroutine with async
println("Throwing exception from async")
throw ArithmeticException() // Nothing is printed, relying on user to call await
}
try {
deferred.await()
println("Unreached")
} catch (e: ArithmeticException) {
println("Caught ArithmeticException")
}
}
//sampleEnd
完整代码见官方示例。
原文示例输出(启用调试模式),本任务未执行:
Throwing exception from launch
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException
CoroutineExceptionHandler
未捕获异常默认会打印到控制台,可以自定义这一行为。根协程中的 CoroutineExceptionHandler 上下文元素类似一个通用的 catch 块,用于根协程及其子协程的自定义异常处理。它与 Java 的 Thread.uncaughtExceptionHandler 类似。
无法在 CoroutineExceptionHandler 中从异常恢复:处理器被调用时,协程已经因对应异常结束。通常它用来记录异常、显示错误信息、终止应用或重启应用。
CoroutineExceptionHandler 只处理未捕获异常,也就是尚未通过其他方式处理的异常。具体来说,普通子协程是在另一个 Job 的上下文中创建的,会把异常处理委托给父协程;父协程又继续向上委托,直至根协程。因此,安装在这些子协程自身上下文中的 CoroutineExceptionHandler 不会被使用。
此外,async 构建器会捕获全部异常,并把它们保存在返回的 Deferred 对象中,因此它的 CoroutineExceptionHandler 也不起作用。
监督作用域中的协程不会把异常传播给父协程,因此不适用上述传播规则。后面的监督部分会进一步说明。
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
throw AssertionError()
}
val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
}
joinAll(job, deferred)
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
CoroutineExceptionHandler got java.lang.AssertionError
取消与异常
取消与异常密切相关。协程内部通过 CancellationException 表示取消,各异常处理器会忽略它。因此,它应当只作为额外调试信息的来源,这些信息可以在 catch 块中取得。
通过 Job.cancel 取消协程时,它会终止,但不会取消自己的父协程。
import kotlinx.coroutines.*
fun main() = runBlocking {
//sampleStart
val job = launch {
val child = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("Child is cancelled")
}
}
yield()
println("Cancelling child")
child.cancel()
child.join()
yield()
println("Parent is not cancelled")
}
job.join()
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
Cancelling child
Child is cancelled
Parent is not cancelled
如果协程遇到 CancellationException 以外的异常,它会带着该异常取消父协程。这一行为无法覆盖,用于为结构化并发提供稳定的协程层级。普通子协程不会使用自身的 CoroutineExceptionHandler 实现处理异常。
本文这些示例始终把 CoroutineExceptionHandler 安装在通过 GlobalScope 创建的协程上。在主 runBlocking 作用域中启动的普通子协程上安装处理器没有意义:无论是否安装处理器,只要子协程以异常结束,主协程都会被取消。
只有当全部子协程都终止后,父协程才会处理最初的异常。下面的示例展示这一点。
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) {
launch { // the first child
try {
delay(Long.MAX_VALUE)
} finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(100)
println("The first child finished its non cancellable block")
}
}
}
launch { // the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
job.join()
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
CoroutineExceptionHandler got java.lang.ArithmeticException
异常聚合
当一个协程的多个子协程都因异常失败时,通用规则是“第一个异常获胜”,也就是处理第一个异常。之后发生的其他异常会以 suppressed exception 的形式附加到第一个异常上。
import kotlinx.coroutines.*
import java.io.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
} finally {
throw ArithmeticException() // the second exception
}
}
launch {
delay(100)
throw IOException() // the first exception
}
delay(Long.MAX_VALUE)
}
job.join()
}
完整代码见官方示例。
原文示例输出,本任务未执行:
CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
原文说明,这种聚合机制仅适用于 Java 1.7 及以上版本,并将 JS 与 Native 的限制描述为暂时限制,预计以后解除。这是原文的平台说明与计划,不表示本任务已经验证了各平台的现行支持情况。
取消异常具有透明性,默认会被解包,显露最初的异常:
import kotlinx.coroutines.*
import java.io.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) {
val innerJob = launch { // all this stack of coroutines will get cancelled
launch {
launch {
throw IOException() // the original exception
}
}
}
try {
innerJob.join()
} catch (e: CancellationException) {
println("Rethrowing CancellationException with original cause")
throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
}
}
job.join()
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
Rethrowing CancellationException with original cause
CoroutineExceptionHandler got java.io.IOException
监督
前面已经看到,普通协程的失败会形成双向传播关系,沿整个协程层级扩散。下面考虑只需要单向失败传播的情况。
一个典型例子是 UI 组件,它在自己的作用域中定义任务。如果某个 UI 子任务失败,未必需要取消整个 UI 组件;但如果 UI 组件被销毁,其任务也被取消,就必须取消全部子任务,因为这些任务的结果已经不再需要。
另一个例子是服务器进程:它启动多个子任务,需要监督它们的执行、跟踪失败,并且只重启失败的任务。
SupervisorJob
SupervisorJob 可以满足这些需求。它与普通 Job 类似,区别在于某个子协程的失败或取消不会传播给 supervisor job,也不会传播给其他子协程。下面的示例展示这种行为:
import kotlinx.coroutines.*
fun main() = runBlocking {
//sampleStart
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// launch the first child -- its exception is ignored for this example (don't do this in practice!)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// launch the second child
val secondChild = launch {
firstChild.join()
// Cancellation of the first child is not propagated to the second child
println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// But cancellation of the supervisor is propagated
println("The second child is cancelled because the supervisor was cancelled")
}
}
// wait until the first child fails & completes
firstChild.join()
println("Cancelling the supervisor")
supervisor.cancel()
secondChild.join()
}
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
The first child is failing
The first child is cancelled: true, but the second one is still active
Cancelling the supervisor
The second child is cancelled because the supervisor was cancelled
supervisorScope
对于具有明确作用域的并发,可以使用 supervisorScope 替代 coroutineScope。它只在一个方向上传播取消;只有作用域自身失败时,才会取消全部子协程。与 coroutineScope 相同,它也会等待全部子协程结束后才完成。
import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main() = runBlocking {
//sampleStart
try {
supervisorScope {
val child = launch {
try {
println("The child is sleeping")
delay(Long.MAX_VALUE)
} finally {
println("The child is cancelled")
}
}
// Give our child a chance to execute and print using yield
yield()
println("Throwing an exception from the scope")
throw AssertionError()
}
} catch(e: AssertionError) {
println("Caught an assertion error")
}
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
The child is sleeping
Throwing an exception from the scope
The child is cancelled
Caught an assertion error
受监督协程中的异常
普通任务与监督任务的另一项重要区别是异常处理。每个受监督的子协程都应通过异常处理机制自行处理异常,因为子协程的失败不会传播给父协程。
因此,直接在 supervisorScope 内启动的协程,与根协程一样,会使用安装在自身作用域中的 CoroutineExceptionHandler;详情见前面的 CoroutineExceptionHandler 部分。
import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main() = runBlocking {
//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
supervisorScope {
val child = launch(handler) {
println("The child throws an exception")
throw AssertionError()
}
println("The scope is completing")
}
println("The scope is completed")
//sampleEnd
}
完整代码见官方示例。
原文示例输出,本任务未执行:
The scope is completing
The child throws an exception
CoroutineExceptionHandler got java.lang.AssertionError
The scope is completed
来源:JetBrains 与 kotlinx.coroutines 文档贡献者,Coroutine exceptions handling。正文与示例的实际来源为官方 kotlinx.coroutines 文档仓库,按该仓库的 Apache License 2.0 使用,核对日期 2026-10-03。
修改内容:全部正文译为中文,移除官网运行控件与内部文档构建标记,保留 9 段 Kotlin 代码、9 段原文输出、官方示例和 API 链接;原文关于 Java、JS、Native 的说明按原文语境呈现。本任务没有编译或运行这些示例,也不把 GlobalScope 示例当作生产生命周期设计。
Apache License 2.0 许可全文
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
Definitions.
“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
-
Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
-
Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
-
Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
-
Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
-
Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
-
Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
-
Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
-
Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.











暂无评论内容