Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
관리 메뉴

백엔드 개발 블로그

[Kotlin] 코틀린 launch, async 차이점 본문

Kotlin

[Kotlin] 코틀린 launch, async 차이점

베꺼 2022. 8. 23. 23:30

Kotlin 1.5 기준으로 작성된 글입니다.

  • 코틀린 코루틴에서 새로운 경량 쓰레드에서 작업을 생성하기 위해선 launch 혹은 async를 사용한다.
  • 둘의 자세한 차이점 및 사용시 주의사항에 대해 알아보자.

TL;DR

  • launch는 작업을 실행만 시키고 그 결과에 관심이 없으며, 각 job 마다의 오류를 처리할 필요가 없을 경우 사용한다.
    • launch 내에서 발생한 오류는 join() 메소드 호출 시 catch할 수 없으며, 오류는 부모 job으로 전파된다.
    • 만약 supervisorScope 등을 사용하여 부모 job으로 전파되지 않는 경우, 오류는 unhandled exception으로 취급되어 프로그램 실행에 영향을 끼칠 수 있다.
  • async는 작업 실행의 결과(반환값)를 얻어올 수 있으며 각 job 마다 발생한 오류를 처리할 수 있다.

작업 실행의 결과 포함 여부

launch

  • launchJob 객체를 반환한다.
  • 반환된 Job이 완료되기까지 기다려야할 경우 join을 사용하여 기다릴 수 있다.
val job = launch {
    log.info("Launch!")
    delay(1000)
}
job.join()
  • joinAll()을 사용하여 여러 job의 완료를 기다릴 수 있다.
val job1 = launch {
    log.info("Launch1")
    delay(1000)
}
val job2 = launch {
    log.info("Launch2")
    delay(1000)
}
joinAll(job1, job2)
  • 하지만 launch를 사용하여 반환된 Job에서는 경량 쓰레드에서 수행된 결과를 직접 얻어올 수 없다.
val job = launch {
    doSomething()
}
job.join() // join()의 반환 타입은 Unit 타입이며, doSomething()에서 실행된 결과를 얻어올 수 없다.

async

  • asyncDeferred 객체를 반환한다.
  • 반환된 Deferred가 완료되기까지 기다려야할 경우 await을 사용하여 기다릴 수 있다.
val deferred = async {
    log.info("Async!")
    delay(1000)
}
deferred.await()
  • awaitAll()을 사용하여 여러 job의 완료를 기다릴 수 있다.
val deferred1 = async {
    log.info("Async1")
    delay(1000)
}
val deferred2 = async {
    log.info("Async2")
    delay(1000)
}
awaitAll(deferred1, deferred2)
  • async를 사용하여 반환된 Deferred에는 경량 쓰레드에서 수행된 결과가 저장된다.
val deferred = async {
    doSomething()
}
deferred.await() // doSomething()에서 실행된 결과가 반환된다.
  • DeferredJob을 상속하기 때문에, join() 메소드나 joinAll()을 사용할 수 있다.

오류 전파(Exception Propagation) 방식의 차이

launch

  • launch를 사용한 경량 쓰레드에서 발생한 오류는 join() 메소드에서 catch가 불가능하다.
val job = launch {
    log.info("Launch!")
    delay(1000)
    throw RuntimeException("runtime exception")
}

try {
    job.join() // Exception이 발생하지 않아 catch에서 오류를 처리할 수 없다!
} catch (err: Exception) {
    log.error("Error catched from join", err)
}
  • join()에서 오류를 던지는 대신, 발생한 오류는 부모 job으로 전파되어 외부 scope에서 catch해야만 한다.
    • join()에서 오류가 발생하는 경우는 Job이 취소되는 경우 등이 있다.
  • supervisorScope 등을 사용하여 부모 job으로 전파되지 않는 경우 경량 쓰레드 내에서 발생한 오류는 unhandled exception으로 취급된다.
    • 이 경우 안드로이드 애플리케이션이 강제 종료된다거나, 웹 애플리케이션에서는 로깅이 누락되는 등 문제가 있을 수 있으니 주의가 필요하다.

Q: 저는 join() 호출할 때 오류 잘 나던데요?

  • 일반적인 코루틴 스코프 내에서 한 경량 쓰레드에서 오류가 발생한 경우, 모든 자식 job은 취소되고 부모 job으로 exception이 전파된다.
  • 이 때 자식 job이 취소가 되면서 join() 호출 시 JobCancellationException가 발생한다.

async

  • async를 사용한 경우 await() 메소드를 실행하면서 오류를 처리할 수 있다.
val deferred = async {
    log.info("Async!")
    delay(1000)
    throw RuntimeException("runtime exception")
}

try {
    deferred.await() // Exception이 발생하므로 내부 로직 내에서 에러를 처리할 수 있다.
} catch (err: Exception) {
    log.error("Error catched from await", err)
}
  • 앞서 DeferredJob을 상속하므로 join() 메소드를 사용할 수 있다고 하였다. await()이 아니라 join()을 호출하는 경우 launch와 마찬가지로 오류가 발생하지 않는다.
    • 하지만 launch와 다르게 unhandled exception이 발생하지 않는다.

Q: await() 호출 시 try catch 블럭으로 감쌌는데도 오류가 무시되지 않고 부모 job이 실패해요!

  • 일반적인 코루틴 스코프 내에서 한 경량 쓰레드에서 오류가 발생한 경우, 모든 자식 job은 취소되고 부모 job으로 exception이 전파된다.
  • 취소 처리를 막고 자식 job의 오류를 수동으로 처리하고 싶은 경우 supervisorScope를 이용해보자.

'Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린의 널 안정성 (Null-Safety)  (0) 2022.08.06
Comments