백엔드 개발 블로그
[Kotlin] 코틀린 launch, async 차이점 본문
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
를 이용해보자.
'Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린의 널 안정성 (Null-Safety) (0) | 2022.08.06 |
---|
Comments