1. Cancel() is invalid
When the coroutine task is cancelled, a CancellationException will be generated inside it. The biggest advantage of structured concurrency of coroutines is that if we cancel the parent coroutine, the child coroutines will also be cancelled.
() Not responded
val job = launch() { var i = 0 while (true) { (500L) i++ println("i =$i") } } delay(2000L) () () println("END") } Log
i =1
i =2
i =3
i =4
i =5
i =6
i =7
i =8
i =9
i =10
i =11
i =12
......
The program cannot be stopped
The above program cannot be stopped, and the cancellation of coroutine tasks requires cooperation. The coroutine is cancelled externally, and the coroutine needs to respond within it. After we call (), the coroutine task is no longer active, but the code does not use isActive as a loop condition, so the coroutine cannot be truly cancelled.
You can add state judgment to the coroutine body:
runBlocking { val job = launch() { var i = 0 while (isActive) { (500L) i++ println("i =$i") } } delay(2000L) () () println("END") }
Log
i =1
i =2
i =3
i =4
END
Process finished with exit code 0
Change the condition of the while loop to while (isActive), which means that only when the coroutine is active, the code inside the loop will continue to be executed. The cancellation of coroutines requires internal cooperation.
2. The structure is destroyed
Coroutines are structured. When we cancel the parent coroutine, the child coroutines will also be cancelled.
However, in particular, the nested child coroutines will not be cancelled with the parent coroutine.
runBlocking { val parentJob = launch(fixedDispatcher) { launch(Job()) { var i = 0 while (isActive) { (500L) i++ println("First i:$i") } } launch { var i = 0 while (isActive) { (500L) i++ println("Second i:$i") } } } delay(2000L) () () println("End") }
Log
Second i:1
First i:1
First i:2
Second i:2
Second i:3
First i:3
Second i:4
First i:4
End
First i:5
First i:6
First i:7
First i:8
First i:9
First i:10
First i:11
First i:12
First i:13
First i:14
First i:15
......
It can be found that when creating a child coroutine, using launch(Job()){} is used to break the original coroutine structure. Because the parent Job of the coroutine created by launch(Job()){} is the Job object passed in the launch. Therefore, when calling () , the coroutine cannot be destroyed.
It can be modified as follows:
runBlocking { val parentJob = launch(fixedDispatcher) { launch { var i = 0 while (isActive) { (500L) i++ println("First i:$i") } } launch { var i = 0 while (isActive) { (500L) i++ println("Second i:$i") } } } delay(2000L) () () println("End") }
First i:1
Second i:1
First i:2
Second i:2
First i:3
Second i:3
First i:4
Second i:4
End
parentJob is a parent-child relationship with its child coroutine inside, so both of them respond to events cancelled by coroutines. Don’t easily break the father-son structure of coroutines!
3. CancellationException not handled correctly
For the suspend functions provided by Kotlin, the cancellation of the coroutine can be automatically responded to.
For example:
runBlocking { val parentJob = launch() { launch { var i = 0 while (true) { delay(500L) i++ println("First i = $i") } } launch { var i = 0 while (true) { delay(500L) i++ println("Second i = $i") } } } delay(2000L) () () println("End") }
First i = 1
Second i = 1
First i = 2
Second i = 2
First i = 3
Second i = 3
End
Process finished with exit code 0
The delay() function can automatically detect whether the current coroutine has been cancelled. If it has been cancelled, it will throw a CancellationException, thereby terminating the current coroutine.
runBlocking { val parentJob = launch() { launch { var i = 0 while (true) { try { delay(500L) } catch (e: CancellationException) { println("Catch CancellationException") throw e } i++ println("First i =$i") } } launch { var i = 0 while (true) { delay(500L) i++ println("Second i = $i") } } } delay(2000L) () () println("END") }
Log:
First i =1
Second i = 1
First i =2
Second i = 2
First i =3
Second i = 3
Catch CancellationException
END
Process finished with exit code 0
After the try-catch wraps delay(), it prints out "Catch CancellationException", which means that delay() can indeed automatically respond to the cancellation of the coroutine and generates a CancellationException exception.
Note: If the CancellationException is caught, it will not be re-throwed, which will cause the child coroutine to be cancelled normally.
runBlocking { val parentJob = launch() { launch { var i = 0 while (true) { try { delay(500L) } catch (e: CancellationException) { println("Catch CancellationException") //throw e } i++ println("First i =$i") } } launch { var i = 0 while (true) { delay(500L) i++ println("Second i = $i") } } } delay(2000L) () () println("END") }
......
First i =656179
Catch CancellationException
First i =656180
Catch CancellationException
First i =656181
Catch CancellationException
First i =656182
Catch CancellationException
First i =656183
Catch CancellationException
.....
Therefore, after catching the CancellationException, you need to consider whether it should be re-throwed.
2. Try-catch does not work
runBlocking { try { launch { delay(100L) 1 / 0 } } catch (e: Exception) { println("catch: $e") } delay(500L) println("End") }
Log
Exception in thread "main" : / by zero
at $testTryCatch8$1$(:225)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch8(:221)
at (:15)
at ()
Process finished with exit code 1
It can be found that try-catch did not successfully catch the exception. The program waited for about 100 milliseconds and eventually crashed.
Using async
runBlocking { var deffered: Deferred<Any>? = null try { deffered = async { delay(100L) 1 / 0 } } catch (e: ArithmeticException) { println("Catch:$e") } deffered?.await() println("End") }
Exception in thread "main" : / by zero
at $testTryCatch9$1$(:242)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch9(:237)
at (:16)
at ()
Process finished with exit code 1
When the "1/0" in the coroutine body is executed, the program has already jumped out of the scope of try-catch, so try-catch is invalid.
Move the try-catch into the launch{} coroutine body. The ArithmeticException exception can be caught normally.
runBlocking { var deffered: Deferred<Any>? = null deffered = async { try { delay(100L) 1 / 0 } catch (e: ArithmeticException) { println("Catch:$e") } } () println("End") }
Log
Catch:: / by zero
End
Process finished with exit code 0
Note: Do not use try-catch to directly wrap launch or async.
Use try-catch to wrap "()".
example:
runBlocking { var deffered = async { delay(100L) 1 / 0 } try { () } catch (e: Exception) { println("Catch:$e") } println("End") }
atch:: / by zero
End
Exception in thread "main" : / by zero
at $testTryCatch11$1$deffered$(:275)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch11(:272)
at (:16)
at ()
Process finished with exit code 1
If await() is not called, will the exception in async occur?
runBlocking { var deffered = async { delay(100L) 1 / 0 } delay(500L) println("End") }
Exception in thread "main" : / by zero
at $testTryCatch12$1$deffered$(:290)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch12(:287)
at (:16)
at ()
Process finished with exit code 1
It can be seen that an exception occurs in async, and even if await() is not called, it will also cause the program to crash.
3. SupervisorJob
Use try-catch to wrap "()" and need to be used with SupervisorJob. Implement "If you don't call await(), you won't cause an exception and crash."
unBlocking { val scope = CoroutineScope(SupervisorJob()) { delay(100L) 1 / 0 } delay(500L) println("End") }
Log
End
Process finished with exit code 0
After creating a scope with SupervisorJob, after starting the coroutine with {}, as long as "()" is not called, the program will not crash due to exceptions.
runBlocking { val coroutineScope = CoroutineScope(SupervisorJob()) val deferred = { delay(100L) 1 / 0 } try { () } catch (e: Exception) { println("Catch:$e") } delay(500L) println("End") }
Log
Catch:: / by zero
End
Process finished with exit code 0
The coroutine was created using "{}" and also wrapped "()" with try-catch, so that the exception was successfully caught.
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent) public interface CompletableJob : Job { public fun complete(): Boolean public fun completeExceptionally(exception: Throwable): Boolean }
SupervisorJob() is not a constructor, it is just a normal top-level function. The object returned by this method is a subclass of Job. The biggest difference between SupervisorJob and Job is that when an exception occurs in its child Job, other child Jobs will not be implicated.
For ordinary jobs, the response strategy when an exception occurs is: since parentJob is an ordinary job object, when an exception occurs in job1, it will cause parentJob to be cancelled, which will also cause job2 and job3 to be implicated.
If parentJob is changed to SupervisorJob, if an exception occurs in job1, it will not affect other jobs.
Note: Use SupervisorJob flexibly to control the range of abnormal propagation.
4. CoroutineExceptionHandler
runBlocking { val coroutineScope = CoroutineScope(coroutineContext) { async { delay(100L) } launch { delay(100L) 1/0 } } delay(1000L) println("END") }
Exception in thread "main" : / by zero
at $testTryCatch15$1$1$(:338)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch15(:329)
at (:16)
at ()
Process finished with exit code 1
Use CoroutineExceptionHandler to handle exceptions in the above code.
runBlocking { val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("Catch: $throwable") } val coroutineScope = CoroutineScope(coroutineContext + Job() + coroutineExceptionHandler) { async { delay(100L) } launch { delay(100L) 1 / 0 } } delay(1000L) println("END") }
Log
Catch: : / by zero
END
Process finished with exit code 0
Define a CoroutineExceptionHandler and pass it into the scope to catch all exceptions.
Note: In a specific scenario, why does CoroutineExceptionHandler not work?
runBlocking { val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("Catch: $throwable") } val coroutineScope = CoroutineScope(coroutineContext) { async { delay(100L) } launch(coroutineExceptionHandler) { delay(100L) 1 / 0 } } delay(1000L) println("END") }
Exception in thread "main" : / by zero
at $testTryCatch17$1$1$(:383)
at (:33)
at (:234)
at (:166)
at (:397)
at (:431)
at $default(:420)
at (:518)
at $(:494)
at (:279)
at (:85)
at .BuildersKt__BuildersKt.runBlocking(:59)
at (Unknown Source)
at .BuildersKt__BuildersKt.runBlocking$default(:38)
at $default(Unknown Source)
at .testTryCatch17(:371)
at (:16)
at ()
Process finished with exit code 1
Put the customized myExceptionHandler in the launch where the exception occurs and pass it in. myExceptionHandler does not work, and the exception will not be caught by it. Note: the myExceptionHandler is directly defined at the location where the exception occurs and does not take effect, but the definition can take effect at the top level. Because it only works in top-level coroutines. That is to say, when an exception occurs in the child coroutine, they will report it to the top-level parent coroutine, and then the top-level parent coroutine will call the CoroutineExceptionHandler to handle the corresponding exception. So it is important to remember: Use CoroutineExceptionHandler to handle coroutine exceptions of complex structures, which only works in top-level coroutines.
This is the end of this article about the detailed explanation of Kotlin try catch exception handling. For more related Kotlin try catch content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!