能正确地提出问题便是迈出了创新的第一步。
想出新方法的人在他的方法没有成功曾经,人家总说他是异想天开。
导航
文章目录
- Kotlin 协程的反常处理
- 概述
- 反常处理六种方法
- 一:协程的撤销需求内部合作
- 二:捕获CancellationException需求从头抛出来
- 三:遵循协程的父子结构
- 四:不要用try-catch直接包裹launch、async
- 五:SurpervisorJob的运用
- 六:运用CoroutineExceptionHandler处理杂乱结构的协程反常
- 总结
Kotlin 协程的反常处理
概述
协程是互相协作的程序,协程是结构化的。
如果把Java的反常处理机制,照搬到Kotlin协程中,一定会遇到很多的坑。
Kotlin协程中的反常首要分两大类
- 协程撤销反常(CancellationException)
- 其他反常
反常处理
- 协程的撤销需求内部合作
- 不要轻易打破协程的父子结构
- 捕获了
CancellationException
后,需求考虑是否从头抛出来 - 不要用
try-catch
直接包裹launch
、async
- 灵敏运用
SurpervisorJob
,操控反常传达的规模 - 运用
CoroutineExceptionHandler
处理杂乱结构的协程反常,仅在顶层协程中起效果
核心理念:协程是结构化的,反常传达也是结构化的。
一:协程的撤销需求内部合作
问题:cancel不被呼应
fun main() = runBlocking {
val job = launch(Dispatchers.Default) {
var i = 0
while (true) {
Thread.sleep(500L)
i++
println("i= $i")
}
}
delay(2000L)
job.cancel()
job.join()
println("end")
}
/*
输出成果:
i= 1
i= 2
i= 3
... //不会中止,一直履行
*/
处理:运用isActive判别是否处于活动状况
fun main() = runBlocking {
val job = launch(Dispatchers.Default) {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("i= $i")
}
}
delay(2000L)
job.cancel()
job.join()
println("end")
}
/*
输出成果:
i= 1
i= 2
i= 3
i= 4
end
*/
二:不要打破协程的父子结构
问题:子协程不会跟从父协程一同撤销
协程的优势在于结构化并发,它的许多特性都是树立这个特性之上的,如果咱们无意中打破了它的父子结构关系,就会导致协程代码无法按照预期履行。
val fixedDispatcher = Executors.newFixedThreadPool(2) {
Thread(it, "MyFixedThread").apply { isDaemon = false }
}.asCoroutineDispatcher()
fun main() = runBlocking {
val parentJob = launch(fixedDispatcher) {
launch(Job()) {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("子协程1:i= $i")
}
}
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("子协程2:i= $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("end")
}
/*
输出成果:
子协程2:i= 1
子协程1:i= 1
子协程2:i= 2
子协程1:i= 2
子协程1:i= 3
子协程2:i= 3
子协程2:i= 4
子协程1:i= 4
end
子协程1:i= 5
子协程1:i= 6
...... //子协程1不会中止
*/
说明:协程是结构化的,通常情况下,当撤销了父协程,子协程也会被撤销。可是在这里,子协程1不再是parentJob的子协程,打破了原有的结构化关系。
处理:不损坏父子结构
fun main() = runBlocking {
val parentJob = launch(fixedDispatcher) {
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("子协程1:i= $i")
}
}
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("子协程2:i= $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("end")
}
/*
输出成果:
子协程1:i= 1
子协程2:i= 1
子协程2:i= 2
子协程1:i= 2
子协程1:i= 3
子协程2:i= 3
子协程1:i= 4
子协程2:i= 4
end
*/
三:捕获CancellationException需求从头抛出来
上述一中的Thread.sleep
能够替代为delay
,delay()
函数能够自动检测当时的协程是否已经被撤销,如果已经被撤销,则会抛出CancellationException
反常,然后停止当时的协程。
协程是CancellationException
反常来实现结构化撤销的,有的时分咱们出于某些目的需求捕获CancellationException
反常,但捕获完以后,还需求考虑是否从头抛出来。
问题:捕获CancellationException导致崩溃
fun main() = runBlocking {
val parentJob = launch(Dispatchers.Default) {
launch {
var i = 0
while (true) {
try {
delay(500L)
} catch (e: CancellationException) {
println("捕获CancellationException反常")
}
i++
println("协程1 i= $i")
}
}
launch {
var i = 0
while (true) {
delay(500L)
i++
println("协程2 i= $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("end")
}
/*
输出成果:
协程1 i= 1
协程2 i= 1
协程1 i= 2
协程2 i= 2
协程1 i= 3
协程2 i= 3
捕获CancellationException反常
...... //程序不会停止
*/
处理:需求从头抛出
fun main() = runBlocking {
val parentJob = launch(Dispatchers.Default) {
launch {
var i = 0
while (true) {
try {
delay(500L)
} catch (e: CancellationException) {
println("捕获CancellationException反常")
throw e
}
i++
println("协程1 i= $i")
}
}
launch {
var i = 0
while (true) {
delay(500L)
i++
println("协程2 i= $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("end")
}
/*
输出成果:
协程1 i= 1
协程2 i= 1
协程2 i= 2
协程1 i= 2
协程2 i= 3
协程1 i= 3
捕获CancellationException反常
end
*/
四:不要用try-catch直接包裹launch、async
问题:try-catch不起效果
协程的代码履行顺序与一般程序不一样,直接运用try-catch
包裹launch、async是不会有任何效果的。
fun main() = runBlocking {
try {
launch {
delay(100L)
1 / 0 //产生反常
}
} catch (e: ArithmeticException) {
println("捕获:$e")
}
delay(500L)
println("end")
}
/*
输出成果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
处理:调整效果域
能够将try-catch
移动到协程体内部,这样能够捕获到反常了。
fun main() = runBlocking {
launch {
delay(100L)
try {
1 / 0 //产生反常
} catch (e: ArithmeticException) {
println("捕获:$e")
}
}
delay(500L)
println("end")
}
/*
输出成果:
捕获:java.lang.ArithmeticException: / by zero
end
*/
五:灵敏运用SurpervisorJob
问题:子Job产生反常影响其他子Job
一般Job
,当子Job产生反常时,会导致parentJob撤销,然后导致其他子Job也受到牵连,这也是协程结构化的体现。
fun main() = runBlocking {
launch {
launch {
1 / 0
delay(100L)
println("hello world 111")
}
launch {
delay(200L)
println("hello world 222")
}
launch {
delay(300L)
println("hello world 333")
}
}
delay(1000L)
println("end")
}
/*
输出成果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
处理:1.运用SupervisorJob
SurpervisorJob
是Job
的子类,SurpervisorJob
是一个种特别的Job,能够操控反常的传达规模,当子Job产生反常时,其他的子Job不会受到影响。
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
1 / 0
delay(100L)
println("hello world 111")
}
scope.launch {
delay(200L)
println("hello world 222")
}
scope.launch {
delay(300L)
println("hello world 333")
}
delay(1000L)
println("end")
}
/*
输出成果:
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.ArithmeticException: / by zero
hello world 222
hello world 333
end
*/
处理:2.运用supervisorScope
supervisorScope
底层运用是SupervisorJob
。
fun main() = runBlocking {
supervisorScope {
launch {
1 / 0
delay(100L)
println("hello world 111")
}
launch {
delay(200L)
println("hello world 222")
}
launch {
delay(300L)
println("hello world 333")
}
}
delay(1000L)
println("end")
}
/*
输出成果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
hello world 222
hello world 333
end
*/
六:运用CoroutineExceptionHandler处理杂乱结构的协程反常
Kotlin供给了CoroutineExceptionHandler
处理杂乱的协程嵌套结构,能够捕获整个效果域内的一切反常,只在顶层协程中起效果。
问题:处理杂乱结构的协程反常
处理:运用CoroutineExceptionHandler
fun main() = runBlocking {
val myExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
println("捕获反常:$throwable")
}
val scope = CoroutineScope(SupervisorJob() + myExceptionHandler)
scope.launch {
1 / 0
delay(100L)
println("hello world 111")
}
scope.launch {
delay(200L)
println("hello world 222")
}
scope.launch {
delay(300L)
println("hello world 333")
}
delay(1000L)
println("end")
}
/*
输出成果:
捕获反常:java.lang.ArithmeticException: / by zero
hello world 222
hello world 333
end
*/
总结
-
准则一:协程的撤销需求内部的合作。
-
准则二:不要轻易打破协程的父子结构。协程的优势在于结构化并发,他的许多特性都是树立在这之上的,如果打破了它的父子结构,会导致协程无法按照预期履行。
-
准则三:捕获 CancellationException 反常后,需求考虑是否从头抛出来。协程是依靠 CancellationException 反常来实现结构化撤销的,捕获反常后需求考虑是否从头抛出来。
-
准则四:不要用 try-catch 直接包裹 launch、async。协程代码的履行顺序与一般程序不一样,直接运用 try-catch 或许不会达到预期效果。
-
准则五:运用 SupervisorJob 操控反常传达规模。SupervisorJob 是一种特别的 Job,能够操控反常的传达规模,不会受到子协程中的反常而撤销自己。
-
准则六:运用 CoroutineExceptionHandler 捕获反常。当协程嵌套层级比较深时,能够在顶层协程中定义 CoroutineExceptionHandler 捕获整个效果域的一切反常。