일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- HTTPS
- kotlin
- InnoDB
- Mutation testing
- https go
- go https 구현
- MySQL
- JPA
- 직접 코드로 구현
- https 서버 구현
- Test
- IntelliJ
- cli 만들기
- 돌연변이 테스팅
- standard input
- 코틀린
- ruby
- 오블완
- Pitest
- https implement
- image resizer with go
- https 실습
- AES-GCM
- Java
- 객체지향
- resize image with go
- 자바
- resizer 구현
- 대칭 키 알고리즘
- standard output
- Today
- Total
Rlog
코루틴 빌더 예외처리 본문
Coroutine Exception Handling
Coroutine 의 Exception Handling 은 Coroutine 을 제대로 공부하지 않으면 사용하기 힘들다. 왜냐하면 사용하는 Coroutine Builder 마다 Exception 을 전파하는 방식이 다르기 때문이다.
Coroutine Builder 간의 차이
- launch 의 경우 Exception 이 발생하게 되면 즉시 Exception 을 위로 전파하는 성질이 있습니다.
- async 의 경우 Exception 이 발생해도 즉시 전파하지않고, await() 이 실행될때 전파합니다.
사실 이런 부분은 코드로 봐야 조금 더 직관적이므로 코드로 설명하겠습니다.
launch
launch 의 경우 위에서 설명했듯이 Exception 을 그 즉시 상위로 전파하는 속성을 가지고 있습니다.
suspend fun main() {
val job = runBlocking {
val a = launch {
delay(1000)
throw RuntimeException("Test")
}
println("a is calculated")
val b = launch {
delay(1000)
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
위의 코드에서 a
job 을 실행할때 Exception 이 발생하는 순간 예외가 터지게 됩니다. 그렇다면 예외를 어떻게 처리할 수 있을까요? 가장 간단한 방법으로는 try..catch 를 이용할 수 있습니다.
suspend fun main() {
runBlocking {
val a = launch {
try {
delay(1000)
throw RuntimeException("Test")
} catch (e: java.lang.Exception) {
println(e.message)
}
}
println("a is calculated")
val b = launch {
delay(1000)
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
try..catch 를 이용해서도 손쉽게 Exception 을 컨트롤 할 수 있습니다. 근데 우리가 공통적으로 Exception Handling 하는 로직이 같다면 계속해서 똑같은 try..catch 를 사용하거나, 이를 템플릿으로 만들어서 사용하는 코드 구조가 될것입니다. 그래서 이를 위해 코루틴에는 ExceptionHandler 가 별도로 존재합니다.
val exceptionHandler = CoroutineExceptionHandler { CoroutineContext, throwable ->
println(throwable.message)
}
ExceptionHandler 는 위와 같이 CoroutineContext 를 받으며, throwable 을 통한 공통적인 예외처리가 가능하게 됩니다.
suspend fun main() {
runBlocking {
val a = launch(SupervisorJob() + exceptionHandler) {
delay(1000)
throw RuntimeException("Test")
}
println("a is calculated")
val b = launch {
delay(1000)
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
위와 같이 에러가 나는 부분에 SupervisorJob Context 와 exceptionHandler 를 이용하면 Error 를 try..catch 를 사용하지 않고도 핸들링 할 수 있습니다. (GlobalScope 를 통한 방법도 있으나, GlobalScope 를 개인적으로 쓸 상황은 많지 않다고 생각해서 적지는 않겠습니다.). 만약 runBlocking 에서 공통적으로 에러핸들러를 이용해서 에러를 핸들링 하고 싶다면 어떻게 해야할까요?
suspend fun main() {
runBlocking(SupervisorJob() + exceptionHandler) {
val a = launch {
throw RuntimeException("Test")
}
println("a is calculated")
val b = launch {
delay(1000)
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
이 코드가 ErrorHandling 이 될까요? 정답은 x 입니다. 이 코드는 ErrorHandling 이 되지 않습니다. 왜일까요? 이미 a
의 launch 에서 Exception 이 전파되어 Coroutine 전체가 Cancel 되기 때문입니다. 따라서 exceptionHandler 는 공통으로 사용하고 싶은데, Error 가 터지는 몇개 잡들은 컨트롤 하고 싶어 라고 하면 아래와 같이 코드를 작성하면 됩니다.
suspend fun main() {
runBlocking(exceptionHandler) {
val a = launch(SupervisorJob()) {
throw RuntimeException("Test")
}
println("a is calculated")
val b = launch(SupervisorJob()) {
delay(1000)
throw RuntimeException("ㅋㅋ")
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
만약 runBlocking 은 Thread 를 Blocking 시키니까, Suspend 함수로 하고 싶다면 아래와 같이 코드를 작성할 수 있을 것입니다.
suspend fun main() {
withContext(exceptionHandler2) {
val a = launch(SupervisorJob()) {
throw RuntimeException("Test")
}
println("a is calculated")
val b = launch(SupervisorJob()) {
throw RuntimeException("ㅋㅋ")
20
}
delay(5000)
println(a) // 10
println(b) // 20
}
}
Async
Async 의 경우 위에서 설명했 듯 Exception 이 발생해도 즉시 전파 하지않고, await() 이 실행될때 전파합니다. 이건 await method 의 설명을 보면 조금 더 직관적입니다.
... returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
기본적으로 async 는 deffered 라는 JavaScript 의 Promise 와 비슷한 객체를 만들게 되는데, 이 Deffered 가 내부에서 Exception 이 터지거나, Cancel 등으로 만약 Cancelled 됬다면, 그때 예외를 응답시킵니다.
위에서 많이 설명했으니 기본적으로 Exception Handler 와 SupervisorJob 으로 한번 Catch 해보겠습니다.
suspend fun main() {
runBlocking(exceptionHandler) {
val a = async(SupervisorJob()) {
throw RuntimeException("zzz")
10
}
println("a is calculated")
val b = async(SupervisorJob()) {
delay(1000)
20
}
delay(5000) // 이때 까지 에러 안남.
println(a.await()) // 10
println(b.await()) // 20
}
}
이 코드를 실행시키면 Exception 이 발생하게 됩니다. 이유가 뭘까요? launch 에서는 분명 똑같이 하면 Exception 이 Catch 됬었습니다. 이유는 위에서 설명한대로 await() 에서 예외가 방출되기 때문입니다. 심층적으로 코드를 까보면 더 알기 쉬운데, 이제 쉽게 설명하면 async 작업을 진행하던 도중 예외가 발생하여 Deffered 가 Cancel 상태가 됩니다. 그래서 await() 을 하는 순간 runBlocking Scope 가 취소되게 됩니다. 왜냐하면 runBlocking Scope 에서 터졌기 때문이죠. 그렇다면 어떻게 해야할까요? 정답은 어쩔 수 없이 try..catch 를 이용해야 합니다.
suspend fun main() {
runBlocking {
val a = async {
try {
throw RuntimeException("zzz")
10
} catch (e: java.lang.Exception) {
println(e.message)
}
}
println("a is calculated")
val b = async {
delay(1000)
20
}
delay(1000)
println(a.await()) // 10
println(b.await()) // 20
}
}
위와 같이 적으면 Catch 가 잘됩니다. 그리고 b 또한 무사히 수행됩니다. 근데 우리가 말한대로 await() 에서 예외가 발생하니까, 아래와 같이 작성해도 될까요?
suspend fun main() {
runBlocking {
val a = async {
throw RuntimeException("zzz")
10
}
println("a is calculated")
val b = async {
delay(1000)
20
}
Thread.sleep(5000)
try {
println(a.await()) // 10
println(b.await()) // 20
} catch (e: RuntimeException) {
println(e.message) // 여기서 에러 잡힘
}
}
}
위와 같이 작성하면 결과는 아래와 같습니다.
a is calculated
zzz
Exception in thread "main" java.lang.RuntimeException: zzz
보면 에러가 잡혀서 "zzz" 도 찍혔지만 다시 에러가 전파되는걸 확인할 수 있습니다. 그 이유는 Coroutine 에서 예외가 발생했기 때문에 이미 Job 은 Cancel 되었고, 그 예외가 위로 전파되기 때문입니다. 따라서 위와같이 await() 에서 try..catch 를 작성해야 한다면 아래와 같이 코드를 작성해야 합니다.
suspend fun main() {
runBlocking(exceptionHandler) {
val a = async(SupervisorJob()) {
throw RuntimeException("zzz")
10
}
println("a is calculated")
val b = async {
delay(1000)
20
}
Thread.sleep(5000)
try {
println(a.await()) // 10
println(b.await()) // 20
} catch (e: RuntimeException) {
println(e.message)
}
}
}
위와 같이 작성하면 예외가 전파되지 않습니다.
정리
위와 같은 이유로 Async 는 Exception 을 전파한다라는 단어말고도, 외부로 예외를 표출(expose) 한다 라고 쓰이는 경우도 있습니다. launch 와 async 는 위와 같은 차이를 가지고 있어서 Exception Handling 하실때 유의하여 사용해야 합니다.
'Kotlin' 카테고리의 다른 글
Coroutine withContext 를 이용한 await 처리 (3) | 2022.07.11 |
---|---|
코드스피츠 6강) 코루틴 (0) | 2022.07.07 |
CoroutineScope 과 Runblocking 의 차이 (0) | 2022.07.05 |
Kotlin Coroutine Exception Handling (0) | 2022.07.05 |
Kotlin Coroutine Job (0) | 2022.07.01 |