코루틴 빌더와 Job 객체 개요
- 코루틴을 생성하는 데 사용하는 함수
- ex) runBlocking, launch
- 모든 코루틴 빌더 함수는 코루틴을 만들고 코루틴을 추상화한 Job 객체를 생성
- 코루틴은 일시 중단할 수 있는 작업으로 실행 도중 일시 중단된 후 이후 이어서 실행될 수 있음
- 코루틴을 추상화한 Job 객체는 이에 대응해 코루틴을 제어할 수 있는 함수와 코루틴의 상태를 나타내는 상태 값들을 외부에 노출시킴
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch(Dispatchers.IO) { | |
println("[%${Thread.currentThread().name}] 실행") | |
} | |
} |
부연 설명
- launch 함수 호출 시 코루틴 생성
- Job 객체가 생성되고 반환되는 것을 확인 가능
join을 사용한 코루틴 순차 처리
- Job 객체는 순차 처리가 필요한 상황을 위해 join 함수를 제공
- join 함수를 통해 먼저 처리되어야 하는 코루틴의 실행이 완료될 때까지 호출부의 코루틴을 일시 중단하도록 만들 수 있음
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}] 네트워크 요청") | |
} | |
} |

부연 설명
- runBlocking 코루틴이 updateTokenJob.join()을 호출하면 runBlocking 코루틴은 updateTokenJob 코루틴이 완료될 때까지 일시 중단됨
- join 함수를 호출하지 않을 경우 `토큰 업데이트 작업`과 `네트워크 요청 작업`이 병렬로 동시에 실행됐음
joinAll을 사용한 코루틴 순차 처리
- 실제 개발에서는 서로 독립적인 여러 코루틴을 병렬로 실행한 뒤 실행한 요청들이 모두 끝날 때까지 대기한 뒤 다음 작업을 진행하는 것이 효율적
- 이를 위해 Job 객체는 joinAll 함수를 제공하며 joinAll 함수는 가변 인자로 Job 타입의 객체를 받은 후 각 Job 객체에 대해 모두 join 함수를 호출하는 것이 전부
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
val uploadImageJob: Job = launch(Dispatchers.IO) { | |
println("[${Thread.currentThread().name}] 이미지1,2 업로드") | |
} | |
} |

CoroutineStart.LAZY를 통해 코루틴 지연
- launch 함수를 사용해 코루틴을 생성하면 유휴 쓰레드가 있는 경우 곧바로 실행되지만 나중에 실행돼야 할(지연 시작) 코루틴을 미리 생성해야 하는 케이스도 존재
- 코루틴을 지연 시작하기 위해서는 launch 함수의 start 인자로 CoroutineStart.LAZY를 넘겨 코루틴에 지연 시작 옵션을 적용해야 함
- 지연 코루틴은 명시적으로 실행을 해줘야 함
- Job 객체의 start 함수를 명시적으로 호출해야 함
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val startTime = System.currentTimeMillis() | |
val lazyJob: Job = launch(start = CoroutineStart.LAZY) { | |
println("[${getElapsedTime(startTime)}] 지연 실행") | |
} | |
delay(1000L) | |
lazyJob.start() | |
} | |
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms" |

코루틴 취소하기
- 코루틴 실행 도중 코루틴을 실행할 필요가 없어질 경우 즉시 취소해야 함
- 필요 없는 코루틴을 실행되도록 두면 코루틴을 계속해서 쓰레드를 점유하며 성능 저하를 유발할 수 있음
- 위 문제를 해결하기 위해 Job 객체는 코루틴을 취소할 수 있는 cancel 함수를 제공
1. cancel 함수를 사용해 Job 취소하기
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val startTime = System.currentTimeMillis() | |
val longJob: Job = launch(Dispatchers.Default) { | |
repeat(10) { repeatTime -> | |
delay(1000L) | |
println("[${getElapsedTime(startTime)}] 반복횟수 ${repeatTime}") | |
} | |
} | |
delay(3500L) | |
longJob.cancel() | |
} |

2. cancelAndJoin을 사용한 순차 처리
- Job 객체에 cancel 함수를 호출하면 코루틴은 즉시 취소되는 것이 아니라 Job 객체 내부의 취소 확인용 flag 변수를 `취소 요청됨`으로 변경함으로써 코루틴이 취소돼야 한다는 것만 알림
- 이후 미래의 어느 시점에 코루틴의 취소가 요청됐는지 체크하고 취소됨
- 앞서 Job 객체의 join 함수를 사용하면 코루틴을 순차 처리할 수 있었던 것처럼 cancelAndJoin 함수를 사용하면 취소에 대한 순차 처리가 가능해짐
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val startTime = System.currentTimeMillis() | |
val longJob: Job = launch(Dispatchers.Default) { | |
repeat(10) { repeatTime -> | |
delay(1000L) | |
println("[${getElapsedTime(startTime)}] 반복횟수 ${repeatTime}") | |
} | |
} | |
delay(3500L) | |
longJob.cancelAndJoin() // longJob이 취소될 때까지 runBlocking 코루틴 일시 중단 | |
executeAfterJobCancelled() // 코루틴 취소 후 실행되어야 하는 동작 | |
} | |
fun executeAfterJobCancelled() { | |
println("코루틴 취소 후 실행되어야 하는 동작") | |
} |

3. 코루틴의 취소가 확인되는 시점
- 코루틴은 일반적으로 실행 대기 시점이나 일시 중단 시점에 취소를 확인한 후 취소됨
- while 문을 통해 무한 루프를 실행할 경우 일시 중단 지점이 없기 때문에 일시 중단이 일어날 수 없음
- 위 문제를 해결할 수 있는 세 가지 방법 존재
- delay를 사용한 취소 확인
- yield를 사용한 취소 확인
- CoroutineScope.isActive를 사용한 취소 확인
3.1 delay를 사용한 취소 확인
- delay 함수는 일시 중단 함수(suspend fun)로 선언돼 특정 시간만큼 호출부의 코루틴을 일시 중단시킴
- 앞서 언급했듯이 코루틴은 일시 중단되는 시점에 코루틴의 취소를 확인
- 목적은 달성하지만 불필요하게 작업을 일시 중단시키므로 성능 저하 유발함
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val whileJob: Job = launch(Dispatchers.Default) { | |
while (true) { | |
println("작업 중") | |
delay(1) // 일시 중단 | |
} | |
} | |
delay(100L) | |
whileJob.cancel() | |
} |
3.2 yield를 사용한 취소 확인
- yield 함수가 호출되면 코루틴은 자신이 사용하던 쓰레드를 양보함
- 쓰레드 사용을 양보한다는 것은 쓰레드 사용을 중단한다는 뜻이므로 yield를 호출함으로써 목적 달성 가능
- 하지만 yield 함수 또한 while 문을 한 번 돌 때마다 쓰레드 사용이 양보되면서 일시 중단되는 문제가 발생해 비효율적
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val whileJob: Job = launch(Dispatchers.Default) { | |
while (true) { | |
println("작업 중") | |
yield() | |
} | |
} | |
delay(100L) | |
whileJob.cancel() | |
} |
3.3 CoroutineScope.isActive를 사용한 취소 확인
- CoroutineScope는 코루틴이 활성화됐는지 확인할 수 있는 Boolean 타입의 프로퍼티인 isActive를 제공함
- 코루틴에 취소가 요청되면 isActive 프로퍼티의 값은 false로 바뀜
- 위 방법을 사용하면 코루틴이 잠시 멈추지도 않고 쓰레드 사용을 양보하지 않으면서 계속해서 작업을 할 수 있어 효율적
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val whileJob: Job = launch(Dispatchers.Default) { | |
while (this.isActive) { | |
println("작업 중") | |
} | |
} | |
delay(100L) | |
whileJob.cancel() | |
} |
코루틴의 상태와 Job의 상태 변수
- 코루틴은 6가지 상태를 가짐
- 생성 (New)
- 실행 중 (Active)
- 실행 완료 중 (Completing)
- 실행 완료 (Completed)
- 취소 중 (Cancelling)
- 취소 완료 (Cancelled)
- 이 중 `실행 완료 중` 상태는 이번 게시글 말고 이후 게시글에 다룰 예정

1. Job 객체에서 외부로 공개하는 코루틴의 상태 변수
- Job 객체의 상태 변수들은 코루틴의 상태마다 변화함
- 외부로 공개하는 코루틴의 상태 변수는 총 세 가지로 모두 Boolean 타입
- isActive
- isCancelled
- isCompleted
1.1 isActive
- 코루틴이 활성화돼 있는지의 여부 확인 가능
- 코루틴이 활성화돼 있으면 true
- 활성화돼 있지 않으면 false
1.2 isCancelled
- 코루틴이 취소 요청됐는지의 여부 확인 가능
- 코루틴에 취소 요청되면 true
- 앞서 언급했다시피 isCancelled가 참이더라도 즉시 취소되는 것은 아님
1.3 isCompleted
- 코루틴 실행이 완료됐는지의 여부 확인 가능
- 코루틴의 모든 코드가 실행 완료되면 true 반환
- 혹은 취소 완료되면 true 반환
- 실행 중인 상태에서는 false
2. 코루틴 상태
2.1 생성 (New)
- 코루틴 빌더를 통해 코루틴을 생성하면 코루틴은 기본적으로 생성 상태
- 보통 자동으로 `실행 중` 상태로 넘어감
- 앞서 언급한 CoroutineStart.LAZY를 start 인자로 넘기면 실행 중 상태로 바로 안 바뀜
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch(start = CoroutineStart.LAZY) { | |
delay(1000L) | |
} | |
printJobState(job) | |
} | |
fun printJobState(job: Job) { | |
println( | |
"Job State\n" + | |
"isActive >> ${job.isActive}\n" + | |
"isCompleted >> ${job.isCompleted}\n" + | |
"isCancelled >> ${job.isCancelled}" | |
) | |
} |

2.2 실행 중 (Active)
- 코루틴이 실제로 실행 중일 때뿐만 아니라 실행된 후 일시 중단된 때도 실행 중 상태
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch { // 실행 중 상태의 코루틴 생성 | |
delay(1000L) | |
} | |
printJobState(job) | |
} |
2.3 실행 완료 (Completed)
- 코루틴의 모든 코드가 실행 완료된 상태
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch { | |
delay(1000L) | |
} | |
delay(2000L) | |
printJobState(job) | |
} |
2.4 취소 중 (Cancelling)
- Job.cancel() 등을 통해 코루틴에 취소가 요청됐을 경우 최소 중 상태로 넘어감
- 아직 취소된 상태가 아니어서 코루틴은 계속해서 실행됨
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch(Dispatchers.Default) { // 취소를 확인할 수 있는 시점 | |
while (true) { | |
// 작업 실행 | |
} | |
} | |
job.cancel(); | |
printJobState(job) | |
} |

2.5 취소 완료 (Cancelled)
- 코루틴의 취소 확인 시점에 취소가 확인된 경우 취소 완료 상태로 전환
- 해당 상태에서는 코루틴은 더 이상 실행되지 않음
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun main() = runBlocking<Unit> { | |
val job: Job = launch { | |
delay(5000L) | |
} | |
job.cancelAndJoin() // 코루틴 취소 요청 + 취소가 완료될 때까지 대기 | |
printJobState(job) | |
} |

3. 코루틴 상태 정리
코루틴 상태 | isActive | isCancelled | isCompleted |
생성 (New) | false | false | false |
실행 중 (Active) | true | false | false |
실행 완료 (Completed) | false | false | true |
취소 중 (Cancelling) | false | true | false |
취소 완료 (Cancelled) | false | true | true |
참고
코틀린 코루틴의 정석 (조세영 저)
반응형
'Kotlin > 코틀린 코루틴의 정석' 카테고리의 다른 글
[Kotlin 코루틴] 예외 처리 (0) | 2024.11.22 |
---|---|
[Kotlin 코루틴] 구조화된 동시성 (0) | 2024.11.21 |
[Kotlin 코루틴] CoroutineContext 정리 (0) | 2024.11.19 |
[Kotlin 코루틴] async와 Deferred (0) | 2024.11.15 |
[Kotlin 코루틴] CoroutineDispatcher 정리 (1) | 2024.11.05 |