안녕하세요. 오늘은 코틀린 코루틴의 정석 - CH4를 읽고 정리한 글입니다.
📌 4장에서 다루는 내용
1. join, joinAll 함수를 사용한 코루틴 간 순차처리
2. CoroutineStart.LAZY 사용한 코루틴 지연 시작
3. 코루틴 실행 취소하기
4. 코루틴의 상태
앞 챕터들에서 코루틴 생성 함수 runBlocking과 launch에 대해서 알아봤었어요.
이런 코루틴을 생성하는데 사용되는 함수를 코루틴 빌더 함수라고 합니다.
코루틴 빌더 함수는
코루틴을 만들고, 코루틴을 추상화한 Job 객체를 생성해요.
반환된 Job 객체는 코루틴의 상태 추적과 제어에 사용이 됩니다.
코루틴은 일시 중단 후 나중에 이어서 실행이 가능하기 때문에 상태 추적이 필수적이에요.
fun main() = runBlocking<Unit> {
val job: Job = launch(Dispatchers.IO) { // Job 객체 반환
println("[${Thread.currentThread().name}] 실행")
}
}
이런식으로 launch 호출 → 코루틴 생성 → Job 객체 생성-반환을 합니다.
오늘은 이렇게 생성되는 Job 객체로 코루틴 간 순차처리를 하는 방법, 코루틴 상태 확인-조작하는 방법에 대해서 알아볼게요.
4.1 join, joinAll을 사용한 코루틴 순차 처리
코루틴 간 순차 처리가 필요한 경우가 종종 발생합니다.
예를 들어, 데이터베이스 작업을 순차적으로 처리해야하는 상황, 캐싱된 토큰 값이 업데이트 이후 네트워크 요청 상황 등이 있죠.
Job 객체는 이러한 순차 처리가 필요한 상황을 위해 join 함수를 제공합니다.
순차 처리가 안된다면 어떻게 될까요?
예를 들어, 네트워크 요청 시 인증 토큰이 필요한 상황에
인증 토큰이 업데이트된 후에 네트워크 요청을 실행해야 정상적으로 처리가 되겠죠.
근데 이때 순차 처리가 안되어 인증 토큰 업데이트 전에 네트워크 요청이 되어버리면 문제가 발생합니다.
fun main() = runBlocking<Unit> {
val updateTokenJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] 토큰 업데이트 시작")
delay(100L) // 토큰 업데이트 지연 시간
println("[${Thread.currentThread().name}] 토큰 업데이트 완료")
}
val networkCallJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] 네트워크 요청")
}
}
/*
// 결과:
[DefaultDispatcher-worker-1 @coroutine#2] 토큰 업데이트 시작
[DefaultDispatcher-worker-3 @coroutine#3] 네트워크 요청
[DefaultDispatcher-worker-1 @coroutine#2] 토큰 업데이트 완료
*/
결과를 보면 토큰 업데이트 완료 전 네트워크 요청을 해버린 걸 알 수 있습니다.
순차 처리가 안된거죠.
+) delay와 Thread.sleep의 차이
delay는 지연 시간동안 스레드가 다른 코루틴을 실행할 수 있어서 효율적입니다.
반면에 Thread.sleep은 스레드 블로킹을 하기 때문에 비효율적이죠.

토큰 업데이트와 네트워크 요청을 병렬, 동시 실행해버리면 문제가 발생합니다.
이 문제를 해결하기 위해 순차 처리가 필요해요.
4.1.1 join 함수
Job 객체의 join 함수를 사용하면 순차 처리가 가능합니다.
예를 들어, JobA 코루틴을 완료하고 → JobB 코루틴을 실행하려면
JobB 실행 전에 JobA에 join 함수를 호출하면 됩니다.
그럼 이전에 이야기하던 케이스로 돌아가서
updateTokenJob이 networkCallJob 전에 완료돼야 한다면 어떻게 해야할까요?
fun main() = runBlocking<Unit> {
val updateTokenJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] 토큰 업데이트 시작")
delay(100L)
println("[${Thread.currentThread().name}] 토큰 업데이트 완료")
}
updateTokenJob.join() // updateTokenJob이 완료될 때까지 runBlocking 코루틴 일시 중단
val networkCallJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] 네트워크 요청")
}
}
/*
// 결과:
[DefaultDispatcher-worker-1 @coroutine#2] 토큰 업데이트 시작
[DefaultDispatcher-worker-1 @coroutine#2] 토큰 업데이트 완료
[DefaultDispatcher-worker-1 @coroutine#3] 네트워크 요청
*/
runBlocking 코루틴이 updateTokenJob.join()을 호출해요.
→ updateTokenJob 코루틴이 완료될 때까지 runBlocking 코루틴을 일시 중단하고,
→ updateTokenJob 코루틴이 완료되면 runBlocking 코루틴을 재개합니다.
→ 그 이후 newtworkingCallJob을 실행되는거죠.

join 함수를 호출하는 코루틴이 완료될 때까지 일시중단 됩니다.
이때 join 함수는 일시중단이 가능한 지점(코루틴)에서만 호출이 가능해요.
+) join 함수는 join을 호출한 코루틴만 일시 중단합니다.
이미 실행 중인 다른 코루틴은 중단하지 않아요.
4.1.2 joinAll 함수
실제 개발에서는 서로 독립적인 코루틴을 병렬 실행한 후 실행 요청이 모두 끝날 때까지 기다렸다가 다음 작업을 진행합니다.
예를 들어, SNS 이미지 업로드 기능이 있을 때
복수의 이미지를 선택하면 → 그 이미지들을 모두 변환해서 → 업로드하죠.
이런 경우,
코루틴 1개에 이미지를 하나씩 변환하지 않습니다.
코루틴을 이미지마다 각각 만들어 병렬로 이미지 변환 후 취합하죠.
⇒ 이런 작업을 위해 복수의 코루틴 실행이 모두 끝날 때까지 호출부 코루틴을 일시중단하는 joinAll 함수를 사용합니다.
public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach {
it.join()
}
가변인자 Job을 넘겨주면,
각 job 객체에 대해 join 함수를 호출합니다.
⇒ joinAll의 대상이 된 코루틴들이 끝날 때까지 호출부 코루틴이 일시중단 되는거에요.
이미지 2개 변환 후 변환된 이미지를 서버에 올리는 경우에
val convertImageJob1: Job = launch(Dispatchers.Default){
Thread.sleep(1000L) // 이미지 변환 작업 실행 시간
println("[${Thread.currentThread().name}] 이미지1 변환 완료")
}
val convertImageJob2: Job = launch(Dispatchers.Default){
Thread.sleep(1000L) // 이미지 변환 작업 실행 시간
println("[${Thread.currentThread().name}] 이미지2 변환 완료")
}
val uploadImageJob: Job = launch(Dispatchers.IO){
println("[${Thread.currentThread().name}] 이미지1, 2 업로드")
}
+) 이미지 변환 작업은 CPU 바운드 작업이므로 Dispatchers.Default가 들어가죠. 반면에 업로드 작업은 네트워크 요청 작업이므로 Dispatchers.IO가 들어갑니다.
fun main() = runBlocking<Unit> {
val convertImageJob1: Job = launch(Dispatchers.Default){
Thread.sleep(1000L) // 이미지 변환 작업 실행 시간
println("[${Thread.currentThread().name}] 이미지1 변환 완료")
}
val convertImageJob2: Job = launch(Dispatchers.Default){
Thread.sleep(1000L) // 이미지 변환 작업 실행 시간
println("[${Thread.currentThread().name}] 이미지2 변환 완료")
}
joinAll(convertImageJob1, convertImageJob2) // 이미지 1과 2가 변환될 때까지 대기
val uploadImageJob: Job = launch(Dispatchers.IO){
println("[${Thread.currentThread().name}] 이미지1, 2 업로드")
}
}
/*
// 결과:
[DefaultDispatcher-worker-1 @coroutine#2] 이미지1 변환 완료
[DefaultDispatcher-worker-2 @coroutine#3] 이미지2 변환 완료
[DefaultDispatcher-worker-1 @coroutine#4] 이미지1, 2 업로드
*/

이런 식으로 joinAll 함수를 통한 복수의 코루틴에 대한 순차 처리를 할 수 있습니다.
joinAll 함수의 대상이 된 코루틴은 병렬, 동시 실행이 됩니다. 이후 그 다음 코루틴이 실행되는거죠.
그럼 순차 처리에 대해서는 모두 알아보았습니다.
이제 코루틴을 생성한 후 원하는 시점에 실행해봅시다.
4.2 CoroutineStart.LAZY 사용해 코루틴 지연 시작하기
launch 함수를 사용해 코루틴을 생성하면 사용할 수 있는 스레드가 있는 경우 곧바로 실행이 됩니다.
그럼 나중에 실행할 코루틴을 미리 생성하려면 어떻게 해야할까요?
이런 경우를 위해 코루틴 라이브러리는 생성된 코루틴을 지연 시작Lazy Start할 수 있는 기능을 제공합니다.
fun getElapsedTime(startTime: Long): String =
"지난 시간: ${System.currentTimeMillis() - startTime}ms"
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val immediateJob: Job = launch{
println("[${getElapsedTime(startTime)}] 즉시 실행") // 지난 시간 측정
}
}
/*
// 결과:
[지난 시간: 2ms] 즉시 실행
*/
지난 시간을 계산하는 함수 getElapsedTime을 통해 확인해보니
launch라 만들어지자마자 바로 실행되는걸 알 수 있죠 (지난 시간: 2ms)
지연 시작이란 무엇일까요?
코루틴을 생성한 후 대기상태에 놓여, 실행 요청을 하지 않으면 시작하지 않는 상태를 말합니다.
launch 함수의 start 인자로 CoroutineStart.LAZY를 넣어주면,
코루틴을 지연 코루틴Lazy Coroutine으로 생성할 수 있습니다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val lazyJob: Job = launch(start = CoroutineStart.LAZY) {
println("[${getElapsedTime(startTime)}] 지연 실행")
}
}
위 코드와 같이 작성하면 코드를 실행해도 아무 결과가 나오지 않습니다.
아까 지연 시작이란 코루틴을 생성한 후 대기 상태에 놓여, 실행 요청을 하지 않으면 시작하지 않는다고 했었죠.
위 코드에서는 명시적으로 실행 요청을 하지 안해서 시작을 안한겁니다.
무한 대기 상태에 놓인거죠.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val lazyJob: Job = launch(start = CoroutineStart.LAZY) {
println("[${getElapsedTime(startTime)}] 지연 실행")
}
delay(1000L) // 1초간 대기
lazyJob.start() // 코루틴 실행
}
/*
// 결과:
[main @coroutine#2][지난 시간: 1014ms] 지연 실행
*/
위와 같이 실행하면 1초간 대기하고 코루틴을 실행합니다.
따라서 1초 뒤 지연 실행되었다는 결과를 볼 수 있죠.
이렇게 지연 시작에 대해서 알아보았어요.
그럼 이제 코루틴을 취소하는 방법에 대해 알아봅시다.
4.3 코루틴 취소하기
코루틴을 실행하는 도중에 이 코루틴이 필요가 없어지면 취소해야합니다.
왜일까요? 굳이 실행 중인 코루틴을 취소할 필요가 있을까요?
있습니다.
냅두면 필요없는 작업하느라 스레드를 계속 사용하게 되어 성능 저하가 발생하거든요.
그럼 여기서 필요없는 작업이란 무엇일까요?
예를들어, 특정 페이지를 열었을 때 → 해당 페이지의 데이터가 로드되겠죠
→ 그런데 사실 잘못 들어온거라 그냥 바로 나가버리면요? → 나갔는데도 계속 해당 페이지 데이터가 로드된다면
→ 필요없는 작업을 하느라 성능 저하가 발생하는거죠.
이러한 문제를 해결하기 위해 Job 객체는 코루틴 취소기능인 cancel 함수를 제공합니다.
오래 실행되는 longJob 코루틴을 만들어볼게요.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val longJob: Job = launch(Dispatchers.Default){
repeat(10){ repeatTime ->
delay(1000L) // 1000밀리초 대기
println("[${getElapsedTime(startTime)}] 반복횟수 ${repeatTime}")
}
}
}
/*
// 결과:
[지난 시간: 1015ms] 반복횟수 0
[지난 시간: 2022ms] 반복횟수 1
[지난 시간: 3028ms] 반복횟수 2
...
[지난 시간: 8050ms] 반복횟수 7
[지난 시간: 9057ms] 반복횟수 8
[지난 시간: 10062ms] 반복횟수 9
*/
0.1초 대기 후 반복횟수를 출력하고 있죠.
이때 3.5초 후에 코루틴을 취소하고 싶다면 어떡할까요?
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val longJob: Job = launch(Dispatchers.Default){
repeat(10){ repeatTime ->
delay(1000L) // 1000밀리초 대기
println("[${getElapsedTime(startTime)}] 반복횟수 ${repeatTime}")
}
}
delay(3500L) // 3500밀리초(3.5초)간 대기
longJob.cancel() // 코루틴 취소
}
/*
// 결과:
[지난 시간: 1015ms] 반복횟수 0
[지난 시간: 2022ms] 반복횟수 1
[지난 시간: 3028ms] 반복횟수 2
*/
위와 같이 취소를 원하는 시점(3.5초 후)에 longJob.cancel()를 호출하면, 3.5초간 대기 후 코루틴을 취소하게 됩니다.
따라서 결과를 보면 3.5초 전까지의 작업만 진행된 것을 확인할 수 있죠.
cancel 함수 호출 이후에 곧바로 다른 작업을 실행하면 → 취소되기 전에 다른 작업이 실행됩니다.
이러면 순차 문제가 발생하죠.
왜 그럴까요?
Job 객체의 cancel 함수를 호출하면
→ 코루틴이 즉시 취소되는 것이 아닌
→ 객체 내부 취소 확인용 플래그를 ‘취소 요청됨’으로 변경하는 겁니다.
→ 따라서 어떠한 시점에 코루틴 취소가 요청됐는지를 확인을 하고
→ 취소가 되는
구조라서 그렇습니다.
⇒ 곧바로 취소하는게 아닌, 어떤 시점에 취소 요청을 확인하고, 취소하는 거죠.
그럼 취소에 대한 순차성을 보장하려면 어떡해야할까요?
cancelAndJoin 함수를 쓰면 됩니다.(얘도 Job 객체 내부에 있어요.)
이 함수는 대상이 된 코루틴의 취소가 완료될 때까지 호출부 코루틴을 일시 중단해요.
fun main() = runBlocking<Unit> {
val longJob: Job = launch(Dispatchers.Default){
// 작업 실행
}
longJob.cancelAndJoin() // longJob이 취소될 때까지 runBlocking 코루틴 일시중단
executeAfterJobCancelled() // 코루틴 취소 후 실행돼야 하는 동작
}
longJob을 실행하면 → runBlocking이 일시 중단되고 → longJob이 완료되면 → executeAfterJobCancelled가 실행됩니다.
순차 처리가 보장되는거죠.
앞서 말했듯이 cancel 함수나 cancelAndJoin 함수를 사용했다고 해서 바로 즉시 취소가 되지는 않습니다.
Job 객체 내부의 취소 확인용 플래그를 바꾸기만 하죠.
코루틴이 이 플래그를 확인하는 시점에 비로소 취소가 됩니다.
따라서 취소 확인 시점이 없으면 취소가 안되는거죠.
그럼 취소 확인 시점은 언제일까요?
일반적으로 일시중단 지점이나 코루틴이 실행을 대기하는 시점입니다.
→ 이 시점이 없다면 취소가 안되겠죠?
fun main() = runBlocking<Unit> {
val whileJob: Job = launch(Dispatchers.Default){
while(true){
println("작업 중")
}
}
delay(100L) // 100밀리초 대기
whileJob.cancel() // 코루틴 취소
}
이 코드의 의도는 “작업 중” 출력을 반복하다가 100밀리초가 지나면 코루틴을 취소하는 것에 있습니다.
그 의도대로 될까요?
안됩니다.
실제로 취소 안됩니다. “작업 중”이 무제한 출력돼요.
왜일까요?
취소를 확인할 수 있는 지점이 없기 때문이에요.
그럼 취소하려면 어떡해야할까요?
- delay를 사용한 취소 확인
- yield를 사용한 취소 확인
- CoroutineScope.isActive를 사용한 취소 확인
위의 방법들로 취소 확인 지점을 만들어주면 됩니다.
4.3.1 delay를 사용한 취소 확인
delay 함수는 일시 중단 함수(suspend fun)로 선언돼 특정 시간만큼 호출부 코루틴을 일시중단합니다.
fun main() = runBlocking<Unit> {
val whileJob: Job = launch(Dispatchers.Default){
while(true){
println("작업 중")
delay(1L)
}
}
delay(100L) // 100밀리초 대기
whileJob.cancel() // 코루틴 취소
}
이러면 100밀리초 뒤 코루틴이 정상적으로 취소가 됩니다.
하지만 while문 반복하는 동안 작업이 강제로 1L씩 일시중단이 되겠죠. 굉장히 비효율적입니다.
불필요한 작업 지연이 반복적으로 이뤄지기 때문에 성능 저하까지 발생하구요.
4.3.2 yield를 사용한 취소 확인
yield가 무슨 뜻인지 아시나요? 바로 양보입니다.
yield 함수를 호출하면 → 코루틴은 자신이 사용하던 스레드를 양보해요.(이름값합니다.)
스레드를 양보한다는 건?
= 스레드 사용을 중단한다는 것이고
= yield를 호출한 코루틴이 일시 중단된다는 뜻이죠.
그래서 바로 그 시점을 활용해 취소를 확인합니다.
fun main() = runBlocking<Unit> {
val whileJob: Job = launch(Dispatchers.Default){
while(true){
println("작업 중")
yield()
}
}
delay(100L) // 100밀리초 대기
whileJob.cancel() // 코루틴 취소
}
이것도 정상적으로 의도대로 취소는 됩니다.
하지만 delay와 마찬가지로 while문을 한번 돌 때마다 스레드를 양보(= 일시 중단)하기 때문에,
코루틴이 아무리 경량스레드라도 매번 일시 중단을 하면 비효율적이겠죠.
4.3.3 CoroutineScope.isActive
CoroutineScope는 코루틴이 활성화됐는지 확인하는 Boolean 타입 프로퍼티 isActive를 제공합니다.
isActive는 취소가 요청되면 false로 변경돼요.
fun main() = runBlocking<Unit> {
val whileJob: Job = launch(Dispatchers.Default){
while(this.isActive){
println("작업 중")
}
}
delay(100L) // 100밀리초 대기
whileJob.cancel() // 코루틴 취소
}
이렇게 명시적으로 코루틴 취소 확인 코드를 넣어주면, 정상적으로 의도대로 코루틴이 취소가 되는 것은 물론 효율성도 챙길 수 있어요.
4.6 코루틴의 상태와 Job의 상태 변수

코루틴은 이러한 6가지 상태를 가질 수 있습니다.
다만 “실행 완료 중”은 7장에서 자세히 다룰 것이기 때문에 이번 장에서는 나머지 5가지 상태만 다뤄볼게요.
- 생성 New
코루틴 빌더를 통해서 생성되면 → 기본적으로 코루틴은 “생성” 상태에 놓입니다.
→ 그 후 자동으로 “실행 중” 상태로 넘어가죠.
(“실행 중”으로 가기 싫다면 CoroutineStart.LAZY로 지연 코루틴으로 만들면 됩니다.) - 실행 중 Active
코루틴을 만들면 자동으로 “실행 중” 상태가 됩니다.
실제로 실행 중일 때뿐만 아니라 실행 후 일시 중단일 때도 “실행 중” 상태에 속해요. - 실행 완료 Completed
코루틴의 모든 코드가 실행 완료된 경우에 “실행 완료”로 넘어갑니다. - 취소 중 Cancelling
Job.cancel() 등을 통해 취소가 요청됐을 경우 “취소 중”으로 넘어갑니다.
취소된 건 아니라 계속 실행 중인 상태에요. - 취소 완료 Cancelled
취소 확인 시점에 취소 확인된 경우 “취소 완료”로 넘어갑니다.
더 이상 실행하지 않아요.
⇒ Job 객체는 코루틴이 위와 같은 상태 중 어떤 상태에 있는지 나타내는 상태 변수들을 통해 외부로 공개합니다.
Job 객체는 코루틴을 추상화한 객체이므로 상태를 간접적으로만 나타내요.
Job 객체의 코루틴 상태변수는 3가지가 있어요.
isActive, isCancelled, isCompleted로, 모두 Boolean 타입 변수입니다.
- isActive
활성화 여부를 나타냅니다. - 활성화면 true, 비활성화면 false - isCancelled
취소 요청 여부를 나타냅니다. - 취소 요청이면 true
요청되기만 하면 true를 반환해요. 따라서 isCancelled가 true더라도 바로 취소가 되지는 않습니다. - isCompleted
실행 완료 여부를 나타냅니다. - 모든 코드가 실행 완료되거나 취소 완료면 ture, 실행 중이면 false
fun main() = runBlocking<Unit> {
val job: Job = launch(start = CoroutineStart.LAZY){ // 생성 상태의 Job 생성
delay(1000L)
}
printJobState(job)
}
/*
// 결과:
JobState
isActive >> false
isCancelled >> false
isCompleted >> false
*/
위 코드를 보시면
CoroutineStart.LAZY를 통해 지연 코루틴을 만들었기 때문에,
실행 중으로 넘어가지 않은 생성 상태의 코루틴인 것을 알 수 있습니다.
따라서 isActive, isCancelled, isCompleted 모두 false죠.
fun main() = runBlocking<Unit> {
val job: Job = launch{ // 실행 중 상태의 Job 생성
delay(1000L)
}
printJobState(job)
}
/*
// 결과:
JobState
isActive >> true
isCancelled >> false
isCompleted >> false
*/
코루틴 빌더로 코루틴을 생성했습니다. CoroutineStart.LAZY를 넣어주지 않았기 때문에 지연 코루틴이 되지 않아, 자연스럽게 곧바로 실행 중 상태로 넘어갔죠.
따라서 isActive만 true입니다.
fun main() = runBlocking<Unit> {
val job: Job = launch{
delay(1000L) // 1초간 대기
}
delay(2000L) // 2초간 대기
printJobState(job)
}
/*
// 결과:
JobState
isActive >> false
isCancelled >> false
isCompleted >> true
*/
1초간 대기를 실행하는 코루틴이 있을 때, 2초 뒤 상태를 찍어본다면 실행 완료 상태겠죠.
따라서 isCompleted만 true입니다.
fun main() = runBlocking<Unit> {
val whileJob: Job = launch(Dispatchers.Default){ // 취소를 확인할 수 있는 시점이 없는 코루틴 생성
while(true){
// 작업 실행
}
}
whileJob.cancel() // 코루틴 취소 요청
printJobState(whileJob) // 취소가 요청됐으나 취소가 되지 않은 코루틴의 상태 출력
}
/*
// 결과:
JobState
isActive >> false
isCancelled >> true
isCompleted >> false
*/
취소를 요청했지만 취소를 확인할 수 있는 지점이 없어 무한반복되고 있는 코드입니다.
취소 중의 상태에 빠지겠죠.
따라서 isCancelled만 true입니다.
어 그런데 취소가 되지않아 코드는 실제로 실행 중인데 왜 isActive는 false일까요?
실제로 코드가 실행 중이더라도 취소가 요청되면 isActive는 false가 되기 때문입니다.
실제 취소 여부가 아닌 취소 요청 여부에 따라 isActive의 활성화가 변한다고 보시면 됩니다.
+) 위 코드에서 while(true) 부분을 while(this.isActive)로 변경해도 됩니다.
이 코드에서 this는 CoroutinScope인데,
CoroutineScope.isActive = Job.isActive이기 때문이에요.
fun main() = runBlocking<Unit> {
val job: Job = launch{
delay(5000L) // 5초간 대기
}
job.cancelAndJoin() // 코루틴 취소 요청 + 취소가 완료될 때까지 대기
printJobState(job)
}
/*
// 결과:
JobState
isActive >> false
isCancelled >> true
isCompleted >> true
*/
취소가 요청되고, 취소 요청을 확인되는 시점에 취소 완료가 됩니다.
따라서 isCancelled와 isCompleted가 모두 true가 됩니다.

위에서 둘러본 이 5가지의 상태를 정리하면 이렇게 표로 나타낼 수 있죠.
코루틴을 깊게 이해하기 위해서는 내부에서 어떤 상태 전이가 발생하는지 제대로 알고 있어야 한다고 합니다.
따라서 이번 코루틴의 상태에 대해서 제대로 이해하고 넘어가야겠네요.
CH4 요약
- runBlocking 함수와 launch 함수는 코루틴 생성용, 코루틴 빌더함수입니다.
- launch 함수를 호출하면 Job 객체가 생성, 반환됩니다.
- 이때 Job 객체는 코루틴 상태 추적, 제어에 사용돼요.
- Job 객체의 join 함수를 호출하면 해당 코루틴이 일시중단됩니다.(실행 완료될 때까지)
- joinAll 함수를 사용해 복수 코루틴이 실행 완료될 때까지 일시 중단을 할 수 있어요.
- Job 객체의 cancel 함수는 코루틴에 취소 요청을 합니다.
- 근데 그 취소요청으로 바로 취소되는게 아니라, 취소 플래그의 상태를 변경해요.
- 그 후에 취소 요청을 확인하면, 취소가 됩니다.
- A를 취소하고 B를 실행하려면 cancel이 아닌 cancelAndJoin을 써야해요.
- cancel 함수를 호출해도 취소를 확인할 수 없으면 취소되지 않고 계속 실행됩니다.
- delay, yield 함수나 isActive 변수로 취소를 확인하는 지점을 만들 수 있어요.
- 코루틴은 생성, 실행 중, 실행 완료, 취소 중, 취소 완료의 상태를 가집니다.
- Job 객체는 isActive, isCancelled, isCompleted 변수를 통해 그 상태를 가집니다.
- isActice는 실행 중일 때만 true입니다.
- isCancelled는 취소 중, 취소 완료일 때만 true입니다.
- isCompleted는 취소 완료, 실행 완료일 때만 true입니다.
- 라이브러리의 효율적 사용을 위해서는 상태 변화에 대한 이해가 중요해요.
이렇게 오늘은 코루틴 빌더와 Job에 대해서 알아보았어요.
이번 챕터에서는 알아가는 내용이 좀 많았는데, 그 중에서도 상태를 인상깊게 읽었습니다.
코루틴을 이해하기 위해서는 상태 전이가 중요하다고 했으니 상태에 대해 명확하게 알고 넘어갈 수 있어서 좋았습니다.
다음에는 CH5 - asyn와 Deferred로 찾아오겠습니다.

'🤖안드로이드🤖' 카테고리의 다른 글
| [안드로이드] merge와 rebase, cherry-pick과 squash (1) | 2025.10.16 |
|---|---|
| [안드로이드] Custom Convention Plugin (1) | 2025.10.12 |
| [안드로이드] CoroutineDispatcher (0) | 2025.10.02 |
| [안드로이드] 코루틴 개발 환경 설정 (0) | 2025.09.17 |
| [안드로이드] 스레드 기반 작업의 한계와 코루틴의 등장 (4) | 2025.09.08 |