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
launch는Job객체를 반환한다.- 반환된
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
async는Deferred객체를 반환한다.- 반환된
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()에서 실행된 결과가 반환된다.
Deferred는Job을 상속하기 때문에,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)
}
- 앞서
Deferred는Job을 상속하므로join()메소드를 사용할 수 있다고 하였다.await()이 아니라join()을 호출하는 경우launch와 마찬가지로 오류가 발생하지 않는다.- 하지만
launch와 다르게 unhandled exception이 발생하지 않는다.
- 하지만
Q: await() 호출 시 try catch 블럭으로 감쌌는데도 오류가 무시되지 않고 부모 job이 실패해요!
- 일반적인 코루틴 스코프 내에서 한 경량 쓰레드에서 오류가 발생한 경우, 모든 자식 job은 취소되고 부모 job으로 exception이 전파된다.
- 취소 처리를 막고 자식 job의 오류를 수동으로 처리하고 싶은 경우
supervisorScope를 이용해보자.