반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
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
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[코루틴의 정석] 8장. 예외처리 본문

Kotlin/코루틴의 정석

[코루틴의 정석] 8장. 예외처리

어리둥절범고래 2024. 9. 18. 23:58
반응형


8.1. 코루틴의 예외 전파

 

8.1.1. 코루틴에서 예외가 전파되는 방식

 

코루틴에서 예외가 발생하면, 그 코루틴은 취소되고 예외가 부모 코루틴으로 전달된다.

부모 코루틴에서도 예외가 처리되지 않으면 루트 코루틴까지 예외가 전파될 수 있다.

 

예외 전파를 제대로 막지 못해 루트 코루틴이 취소되면 구조화된 코루틴이 모두 취소될 수 있다.

 


8.1.2. 예제로 알아보는 예외 전파

fun main() = runBlocking<Unit> {
  launch(CoroutineName("Coroutine1")) {
    launch(CoroutineName("Coroutine3")) {
      throw Exception("예외 발생")
    }
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  launch(CoroutineName("Coroutine2")) {
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code1.Code8_1Kt$main$1$1$1.invokeSuspend(Code8-1.kt:8)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...

Process finished with exit code 1
*/

Coroutine3 코루틴에서 발생한 예외가 모든 코루틴을 취소한 것을 볼 수 있다.

 

 



8.2. 예외 전파 제한

8.2.1. Job 객체를 사용한 예외 전파 제한

 

8.2.1.1. Job 객체를 사용해 예외 전파 제한하기

 

코루틴은 자신의 부모 코루틴으로만 예외를 전파하는 특성을 가지므로 부모 코루틴과의 구조화를 깬다면 예외가 전파되지 않는다.

fun main() = runBlocking<Unit> {
  launch(CoroutineName("Parent Coroutine")) {
    launch(CoroutineName("Coroutine1") + Job()) { // 새로운 Job 객체를 만들어 Coroutine1에 연결
      launch(CoroutineName("Coroutine3")) {
        throw Exception("예외 발생")
      }
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code2.Code8_2Kt$main$1$1$1$1.invokeSuspend(Code8-2.kt:9)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
[main @Coroutine2#4] 코루틴 실행

Process finished with exit code 0
*/

이를 구조화하면 아래와 같다.

 

 

8.2.1.2. Job 객체를 사용한 에외 전파 제한의 한계

 

구조화를 깨는 것은 예외 전파를 제한하는 것뿐만 아니라 취소 전파도 제한시킨다.

fun main() = runBlocking<Unit> {
  val parentJob = launch(CoroutineName("Parent Coroutine")) {
    launch(CoroutineName("Coroutine1") + Job()) {
      launch(CoroutineName("Coroutine3")) { // Coroutine3에서 예외 제거
        delay(100L)
        println("[${Thread.currentThread().name}] 코루틴 실행")
      }
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(20L) // 코루틴들이 모두 생성될 때까지 대기
  parentJob.cancel() // Parent Coroutine에 취소 요청
  delay(1000L)
}
/*
// 결과:
[main @Coroutine1#3] 코루틴 실행
[main @Coroutine3#5] 코루틴 실행

Process finished with exit code 0
*/

이를 구조화하면 아래와 같다.

 

 


8.2.2. SupervisorJob 객체를 사용한 예외 전파 제한


8.2.2.1. SupervisorJob 객체를 사용해 예외 전파 제한하기

 

SupervisorJob 객체는 자식 코루틴으로부터 예외를 전파받지 않는 특수한 Job 객체다.

parent 인자로 객체를 넘기면 부모 Job이 있는 SupervisorJob 객체를 만들 수 있으며, 부모 Job이 없으면 루트 Job이 된다.

public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

 

SupervisorJob 객체는 다음과 같이 사용할 수 있다.

fun main() = runBlocking<Unit> {
  val supervisorJob = SupervisorJob()
  launch(CoroutineName("Coroutine1") + supervisorJob) {
    launch(CoroutineName("Coroutine3")) {
      throw Exception("예외 발생")
    }
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  launch(CoroutineName("Coroutine2") + supervisorJob) {
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code4.Code8_4Kt$main$1$1$1.invokeSuspend(Code8-4.kt:9)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
[main @Coroutine2#3] 코루틴 실행

Process finished with exit code 0
*/

supervisorJob의 다른 자식 코루틴인 Coroutine2 코루틴이 정상 실행되는 것을 볼 수 있다.

 



8.2.2.2. 코루틴의 구조화를 깨지 않고 SupervisorJob 사용하기

 

구조화를 깨지 않고 SupervisorJob을 사용하기 위해서는 SupervisorJob의 인자로 부모 Job 객체를 넘기면 된다.

fun main() = runBlocking<Unit> {
  // supervisorJob의 parent로 runBlocking으로 생성된 Job 객체 설정
  val supervisorJob = SupervisorJob(parent = this.coroutineContext[Job])
  launch(CoroutineName("Coroutine1") + supervisorJob) {
    launch(CoroutineName("Coroutine3")) {
      throw Exception("예외 발생")
    }
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  launch(CoroutineName("Coroutine2") + supervisorJob) {
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  supervisorJob.complete() // supervisorJob 완료 처리
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code5.Code8_5Kt$main$1$1$1.invokeSuspend(Code8-5.kt:9)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
[main @Coroutine2#3] 코루틴 실행

Process finished with exit code 0
*/

 

 


8.2.2.3. SupervisorJob을 CoroutineScope와 함께 사용하기

 

CoroutineScope의 CoroutineContext에 SupervisorJob 객체를 설정하여 사용할 수 있다.

fun main() = runBlocking<Unit> {
  val coroutineScope = CoroutineScope(SupervisorJob())
  coroutineScope.apply {
    launch(CoroutineName("Coroutine1")) {
      launch(CoroutineName("Coroutine3")) {
        throw Exception("예외 발생")
      }
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "DefaultDispatcher-worker-1" java.lang.Exception: 예외 발생
	at chapter8.code6.Code8_6Kt$main$1$1$1$1.invokeSuspend(Code8-6.kt:10)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
[DefaultDispatcher-worker-1 @Coroutine2#3] 코루틴 실행

Process finished with exit code 0
*/

 



8.2.2.4. SupervisorJob을 사용할 때 흔히 하는 실수

 

SupervisorJob 사용 시 흔히 하는 실수는 코루틴 빌더 함수의 context 인자에 SupervisorJob()을 넘기고, 코루틴 빌더 함수가 호출돼 생성되는 코루틴의 하위에 자식 코루틴들을 생성하는 것이다.

fun main() = runBlocking<Unit> {
  launch(CoroutineName("Parent Coroutine") + SupervisorJob()) {
    launch(CoroutineName("Coroutine1")) {
      launch(CoroutineName("Coroutine3")) {
        throw Exception("예외 발생")
      }
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code7.Code8_7Kt$main$1$1$1$1.invokeSuspend(Code8-7.kt:9)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...

Process finished with exit code 0
*/

문제가 생기는 이유는 코루틴 빌더 함수의 context 인자에 Job 객체가 입력될 경우 해당 Job 객체를 부모로 하는 새로운 Job 객체를 만들기 때문이다.

 


8.2.3. supervisorScope를 사용한 예외 전파 제한

 

supervisorScope 함수는 supervisorJob 객체를 가진 CoroutineScope 객체를 생성하며, 이 SupervisorJob 객체는  supervisorScope 함수를 호출한 코루틴의 Job 객체를 부모로 가진다. 즉, supervisorScope 내부에서 실행되는 코루틴은 SupervisorJob과 부모-자식 관계로 구조화된다.

fun main() = runBlocking<Unit> {
  supervisorScope {
    launch(CoroutineName("Coroutine1")) {
      launch(CoroutineName("Coroutine3")) {
        throw Exception("예외 발생")
      }
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: 예외 발생
	at chapter8.code8.Code8_8Kt$main$1$1$1$1.invokeSuspend(Code8-8.kt:9)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
[main @Coroutine2#3] 코루틴 실행

Process finished with exit code 0
*/

 

 



8.3. CoroutineExceptionHandler를 사용한 예외 처리


8.3.1. CoroutineExceptionHandler 생성

 

CoroutineExceptionHandler 함수는 예외를 처리하는 람다식인 handler를 매개 변수로 가진다.

public inline fun CoroutineExceptionHandler(crossinline handler: 
(CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler

handler는 CoroutineContext와 Throwable 타입의 매개변수를 갖는 람다식으로, 이 람다식에 예외가 발생했을 때 어떤 동작을 할지 입력해 예외 처리를 할 수 있다.

val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> 
    println("[예외 발생] ${throwable}")
}


8.3.2. CoroutineExceptionHandler 사용

 

CoroutineExceptionHandler 객체는 CoroutineContext 객체의 구성 요소로 포함될 수 있다.

fun main() = runBlocking<Unit> {
  val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    println("[예외 발생] ${throwable}")
  }
  CoroutineScope(exceptionHandler).launch(CoroutineName("Coroutine1")) {
    throw Exception("Coroutine1에 예외가 발생했습니다")
  }
  delay(1000L)
}
/*
// 결과:
[예외 발생] java.lang.Exception: Coroutine1에 예외가 발생했습니다
*/


8.3.3. 처리되지 않은 예외만 처리하는 CoroutineExceptionHandler

 

만약 자식 코루틴이 부모 코루틴으로 예외를 전파하면 자식 코루틴에서는 예외가 처리된 것으로 봐 자식 코루틴에 설정된 CoroutineExceptionHandler 객체는 동작하지 않는다.

fun main() = runBlocking<Unit> {
  val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    println("[예외 발생] ${throwable}")
  }
  launch(CoroutineName("Coroutine1") + exceptionHandler) {
    throw Exception("Coroutine1에 예외가 발생했습니다")
  }
  delay(1000L)
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: Coroutine1에 예외가 발생했습니다
	at chapter8.code10.Main8_10Kt$main$1$1.invokeSuspend(Main8-10.kt:10)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...
*/


8.3.4. CoroutineExceptionHandler가 예외를 처리하도록 만들기

 

8.3.4.1. Job과 CoroutineExceptionHandler 함께 설정하기

 

CoroutineExceptionHandler 객체가 예외를 처리하게 하는 가장 간단한 방법은 CoroutineExceptionHandler 객체를 루트 Job과 함께 설정하는 것이다.

Job()을 호출하면 새로운 루트 Job을 만들 수 있으므로 이를 사용하면 CoroutineExceptionHandler 객체가 설정되는 위치를 코루틴에서 오류가 처리되는 위치로 만들 수 있다.

fun main() = runBlocking<Unit> {
  val coroutineContext = Job() + CoroutineExceptionHandler { coroutineContext, throwable ->
    println("[예외 발생] ${throwable}")
  }
  launch(CoroutineName("Coroutine1") + coroutineContext) {
    throw Exception("Coroutine1에 예외가 발생했습니다")
  }
  delay(1000L)
}
/*
// 결과:
[예외 발생] java.lang.Exception: Coroutine1에 예외가 발생했습니다
*/


8.3.4.2. SupervisorJob과 CoroutineExceptionHandler 함께 설정하기

 

SupervisorJob 객체는 예외를 전파받지 않을 뿐, 어떤 예외가 발생했는지에 대한 정보를 자식 코루틴으로부터 전파받는다. 따라서 만약 SupervisorJob 객체와 CoroutineExceptionHandler 객체가 함께 설정되면 예외가 처리된다.

fun main() = runBlocking<Unit> {
  val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    println("[예외 발생] ${throwable}")
  }
  val supervisedScope = CoroutineScope(SupervisorJob() + exceptionHandler)
  supervisedScope.apply {
    launch(CoroutineName("Coroutine1")) {
      throw Exception("Coroutine1에 예외가 발생했습니다")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(1000L)
}
/*
// 결과:
[예외 발생] java.lang.Exception: Coroutine1에 예외가 발생했습니다
[DefaultDispatcher-worker-2] 코루틴 실행
*/


8.3.5. CoroutineExceptionHandler는 예외 전파를 제한하지 않는다.

 

CoroutineExceptionHandler는 try-catch문처럼 동작하지 않는다. 예외가 마지막으로 처리되는 위치에서 예외를 처리할 뿐, 예외 전파를 제한하지 않는다.

fun main() = runBlocking<Unit> {
  val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    println("[예외 발생] ${throwable}")
  }
  launch(CoroutineName("Coroutine1") + exceptionHandler) {
    throw Exception("Coroutine1에 예외가 발생했습니다")
  }
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: Coroutine1에 예외가 발생했습니다
	at chapter8.code13.Main8_13Kt$main$1$1.invokeSuspend(Main8-13.kt:10)
  ...
*/


8.4. try catch문을 사용한 예외 처리

 

8.4.1. try catch문을 사용해 코루틴 예외 처리하기

 

코루틴에서 예외가 발생했을 때 일반적인 try catch문을 통해 예외를 처리할 수 있다.

fun main() = runBlocking<Unit> {
  launch(CoroutineName("Coroutine1")) {
    try {
      throw Exception("Coroutine1에 예외가 발생했습니다")
    } catch (e: Exception) {
      println(e.message)
    }
  }
  launch(CoroutineName("Coroutine2")) {
    delay(100L)
    println("Coroutine2 실행 완료")
  }
}
/*
// 결과:
Coroutine1에 예외가 발생했습니다
Coroutine2 실행 완료
*/

8.4.2. 코루틴 빌더 함수에 대한 try catch문은 코루틴의 예외를 잡지 못한다.

 

코루틴 빌더 함수에 try catch문을 사용하면 코루틴에서 발생한 예외가 잡히지 않는다.

fun main() = runBlocking<Unit> {
  try {
    launch(CoroutineName("Coroutine1")) {
      throw Exception("Coroutine1에 예외가 발생했습니다")
    }
  } catch (e: Exception) {
    println(e.message)
  }
  launch(CoroutineName("Coroutine2")) {
    delay(100L)
    println("Coroutine2 실행 완료")
  }
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: Coroutine1에 예외가 발생했습니다
	at chapter8.code15.Main8_15Kt$main$1$1.invokeSuspend(Main8-15.kt:8)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...

Process finished with exit code 1
*/

launch는 코루틴을 생성하는 데 사용되는 함수일 뿐, 람다식 안의 코드가 실제로 실행되는 시점은 코루틴이 CoroutineDispatcher에 의해 스레드에 할당될 때이다. 즉, try-catch문은 launch 함수 자체에서 발생하는 예외만 잡을 수 있고, 람다식 안에서 발생하는 예외는 처리하지 못한다.

 


8.5. async의 예외 처리

 

8.5.1. async의 예외 노출

 

async 코루틴 빌더 함수는 결괏값을 Deferred 객체로 감싸고 await 호출 시점에 결괏값을 노출한다.

코루틴 실행 도중 예외가 발생해 결괏값이 없다면 Deferred에 대한 await 호출 시 예외가 노출된다.

fun main() = runBlocking<Unit> {
  supervisorScope {
    val deferred: Deferred<String> = async(CoroutineName("Coroutine1")) {
      throw Exception("Coroutine1에 예외가 발생했습니다")
    }
    try {
      deferred.await()
    } catch (e: Exception) {
      println("[노출된 예외] ${e.message}")
    }
  }
}
/*
// 결과:
[노출된 예외] Coroutine1에 예외가 발생했습니다
*/

 

 

8.5.2. async의 예외 전파

 

await 함수 호출부에서만 예외를 처리하면 안된다. async 코루틴 빌더 함수도 예외가 발생하면 부모 코루틴으로 예외를 전파하는데 이를 적절하게 처리해야 한다.

fun main() = runBlocking<Unit> {
  async(CoroutineName("Coroutine1")) {
    throw Exception("Coroutine1에 예외가 발생했습니다")
  }
  launch(CoroutineName("Coroutine2")) {
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
}
/*
// 결과:
Exception in thread "main" java.lang.Exception: Coroutine1에 예외가 발생했습니다
	at chapter8.code17.Main8_17Kt$main$1$1.invokeSuspend(Main8-17.kt:7)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	...

Process finished with exit code 1
*/

Coroutine1 코루틴에 await를 호출하는 부분이 없음에도 예외 로그가 나오는 것을 볼 수 있다.

이를 해결하기 위해서는 supervisorScope를 사용해 예외가 전파되지 않도록 만들 수 있다.

fun main() = runBlocking<Unit> {
  supervisorScope {
    async(CoroutineName("Coroutine1")) {
      throw Exception("Coroutine1에 예외가 발생했습니다")
    }
    launch(CoroutineName("Coroutine2")) {
      delay(100L)
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
}
/*
// 결과:
[main @Coroutine2#3] 코루틴 실행
*/


8.6. 전파되지 않는 예외


8.6.1. 전파되지 않는 CancellationException

 

코루틴은 CancellationException 예외가 발생해도 부모 코루틴으로 전파되지 않는다.

fun main() = runBlocking<Unit>(CoroutineName("runBlocking 코루틴")) {
  launch(CoroutineName("Coroutine1")) {
    launch(CoroutineName("Coroutine2")) {
      throw CancellationException()
    }
    delay(100L)
    println("[${Thread.currentThread().name}] 코루틴 실행")
  }
  delay(100L)
  println("[${Thread.currentThread().name}] 코루틴 실행")
}
/*
// 결과:
[main @runBlocking 코루틴#1] 코루틴 실행
[main @Coroutine1#2] 코루틴 실행
*/


8.6.2. 코루틴 취소 시 사용되는 JobCancellationException

 

CancellationException은 코루틴의 취소에 사용되는 특별한 예외이며, Job 객체에 대해 cancel 함수를 호출하면 CancellationException의 서브 클래스인 JobCancellationException을 발생시켜 코루틴을 취소시킨다.

fun main() = runBlocking<Unit> {
  val job = launch {
    delay(1000L) // 1초간 지속
  }
  job.invokeOnCompletion { exception ->
    println(exception) // 발생한 예외 출력
  }
  job.cancel() // job 취소
}
/*
// 결과:
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelled}@7494e528
*/


8.6.3. withTimeOut 사용해 코루틴의 실행 시간 제한하기

 

코루틴 라이브러리는 제한 시간을 두고 작업을 실행할 수 있도록 만드는 withTimeOut 함수를 제공한다.

withTimeOut 함수는 대표적으로 네트워므 호출의 실행 시간을 제한하는 데 사용할 수 있다.

public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T

withTimeOut 함수는 매개변수로 실행 제한 시간(timeMillis)과 작업(block)을 가진다.

fun main() = runBlocking<Unit>(CoroutineName("Parent Coroutine")) {
  launch(CoroutineName("Child Coroutine")) {
    withTimeout(1000L) { // 실행 시간을 1초로 제한
      delay(2000L) // 2초의 시간이 걸리는 작업
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  }
  delay(2000L)
  println("[${Thread.currentThread().name}] 코루틴 실행")
}
/*
// 결과:
[main @Parent Coroutine#1] 코루틴 실행
*/

withTimeOut 함수는 주어진 시간 내에 완료되지 않으면 CancellationException의 서브 클래스인 TimeoutCancellationException을 발생시키며, try catch 문으로 처리될 수도 있다.

fun main() = runBlocking<Unit>(CoroutineName("Parent Coroutine")) {
  try {
    withTimeout(1000L) { // 실행 시간을 1초로 제한
      delay(2000L) // 2초의 시간이 걸리는 작업
      println("[${Thread.currentThread().name}] 코루틴 실행")
    }
  } catch (e: Exception) {
    println(e)
  }
}
/*
// 결과:
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
*/

 

 

추가 자료, withTimeOutOrNull 사용해 실행 시간 초과 시 null 반환 받기

 

withTimeOut 함수는 TimeoutCancellationException이 발생해 코루틴이 취소된다.

실행 시간이 초과되더라도 코루틴이 취소되지 않고 결과가 반환되어야 하는 경우 withTimeOutOrNull 함수를 사용할 수 있다.

fun main() = runBlocking<Unit>(CoroutineName("Parent Coroutine")) {
  launch(CoroutineName("Child Coroutine")) {
    val result = withTimeoutOrNull(1000L) { // 실행 시간을 1초로 제한
      delay(2000L) // 2초의 시간이 걸리는 작업
      return@withTimeoutOrNull "결과"
    }
    println(result)
  }
}
/*
// 결과:
null
*/

 


요약

  1. 애플리케이션은 다양한 예외 상황에 노출되며, 예외를 적절히 처리해 애플리케이션의 안정성을 확보할 수 있다.
  2. 코루틴은 비동기 작업을 실행할 때 사용되기 때문에 애플리케이션의 안정성을 위해 예외 처리가 필수적이다.
  3. 코루틴에서 발생한 예외는 부모 코루틴으로 전파되며, 적절히 처리되지 않으면 최상위 루트 코루틴까지 전파된다.
  4. 예외를 전파받은 코루틴이 취소되면 해당 코루틴의 모든 자식 코루틴에 취소가 전파된다. 즉, 루트 코루틴이 예외로 취소되면 구조화된 모든 코루틴이 취소된다.
  5. 새로운 루트 Job 객체를 통해 코루틴의 구조화를 깨서 코루틴의 예외 전파를 제한할 수 있다.
  6. SupervisorJob 객체를 사용해 예외 전파를 제한할 수 있다. SupervisorJob 객체는 예외를 전파받지 않는 특수한 Job 객체이다.
  7. SupervisorJob 객체는 예외를 전파받지 않지만 예외 정보는 전달받는다.
  8. 예외가 전파되거나 예외 정보가 전달된 경우 해당 코루틴에서 예외가 처리된 것으로 본다.
  9. CoroutineExceptionHandler 객체는 공통 예외 처리기로서 동작하며, 이미 처리된 예외에 대해서는 동작하지 않는다. 즉, 예외가 마지막으로 전파되는 또는 전달되는 위치에 설정되지 않으면 동작하지 않는다.
  10. CoroutineExceptionHandler 객체는 예외 전파를 제한하지 않는다.
  11. 코루틴 내부에서 try catch문을 사용해 예외를 처리할 수 있다.
  12. 코루틴 빌더 함수에 대한 try catch문은 코루틴이 실행될 때 발생하는 예외를 잡지 못한다.
  13. async 함수로 생성된 코루틴에서 발생한 예외는 await 호출 시 노출된다.
  14. async 코루틴에서 발생한 예외 또한 부모 코루틴으로 전파된다.
  15. CancellationException은 다른 예외와 달리 부모 코루틴으로 전파되지 않는다.
  16. CancellationException이 전파되지 않는 이유는 CancellationException은 코루틴을 취소하기 위한 특별한 예외이기 때문이다. Job 객체에 cancel 함수를 호출하면 CancellcationException의 서브 클래스인 JobCancellation Exception이 발생해 코루틴이 취소된다.
  17. withTimeOut 함수를 사용해 코루틴의 실행 시간을 제한할 수 있다. withTimeOut 함수는 실행 시간 초과 시 CancellationException의 서브 클래스인 TimeoutCancellationException을 발생시켜 해당 코루틴만 취소한다.
  18. withTimeOutOrNull을 사용하면 실행 시간 초과 시 null이 반환되도록 할 수 있다.
반응형