1. Join을 사용한 코루틴 순차 처리
코루틴을 사용하다 보면 특정 코루틴이 완료된 후에 다음 코루틴을 실행해야 하는 상황이 발생한다. 이런 순차적인 실행을 위해 Job 객체의 join 함수를 사용할 수 있다.
fun main() = runBlockint<Unit> {
val updateTokenJob = launch(Dispatchers.IO) {
// 토큰 업데이트 작업
}
updateTokenJob.join() // updateTokenJob이 완료될 때까지 대기
val networkCallJob = launch(Dispatchers.IO) {
// 네트워크 호출 작업
}
}
위 예제에서 updateTokenJob.join()이 호출되면, runBlocking 코루틴은 updateTokenJob이 완료될 때까지 대기하고, updateTokenJob이 완료된 후에야 networkCallJob이 실행된다.
이때 사용되는 join() 함수는 일시 중단 함수(suspend function)로, 다음과 같은 순서로 동작한다:
- join()을 호출하는 코루틴(runBlocking)은 잠시 실행을 멈춘다
- join()의 대상이 되는 코루틴(updateTokenJob)이 실행을 완료할 때까지 기다린다
- 대상 코루틴이 완료되면 원래 코루틴이 다시 실행을 시작한다
일시 중단 함수는 코루틴 내부나 다른 일시 중단 함수 안에서만 호출될 수 있기 때문에, join 함수 역시 일반 함수에서는 호출할 수 없고 코루틴 스코프나 suspend 함수 안에서만 호출해야 한다.
2. JoinAll을 사용한 다중 코루틴 순차 처리
여러 개의 코루틴이 모두 완료된 후에 다음 작업을 실행해야 하는 경우가 있다. 이런 경우 Job 객체의 joinAll 함수를 사용하면 여러 코루틴의 완료를 한 번에 대기할 수 있다.
fun main() = runBlocking<Unit> {
val convertImageJob1 = launch(Dispatchers.Default) {
// 이미지1 변환 작업
}
val convertImageJob1 = launch(Dispatchers.Default) {
// 이미지2 변환 작업
}
joinAll(convertImageJob1, convertImageJob2) // 두 Job이 모두 완료될 때까지 대기
val uploadImageJob = launch(Dispatchers.IO) {
// 이미지 업로드
}
}
위 예제에서 joinAll()이 호출되면, runBlocking 코루틴은 convertImageJob1과 convertImageJob2이 모두 완료될 때까지 대기하고, 두 작업이 모두 완료된 후 processJob이 실행된다.
3. 코루틴 지연 시작하기
코루틴을 먼저 생성해 놓고 나중에 실행해야 하는 경우 코루틴 라이브러리가 제공하는 CorutimeStart.LAZY 옵션을 사용할 수 있다. 지연 시작 옵션 적용되어 생성된 코루틴은 지연 코루틴으로 생성된 후 후 대기 상태에 놓이며, 실행을 요청하지 않으면 시작되지 않는다.
fun main() = runBlockint<Unit> {
val startTime = System.currentTimeMillis()
val lazyJob: Job = launch(start = CoroutineStart.LAZY) {
// 지연 실행
}
lazyJob.start() // 코루틴 실행
}
지연 코루틴은 명시적으로 실행을 요청하지 않으면 실행 되지 않기 때문에 위 예제에서처럼 start 함수를 명시적으로 호출하지 않으면 lazyJob이 실행되지 않는다.
4. 코루틴 취소하기
실행 중인 코루틴을 취소할 때는 Job 객체의 cancel 함수와 cancelAndJoin 함수를 사용할 수 있다.
1) cancel() 사용
fun main() = runBlocking<Unit> {
val longJob: Job = launch(Dispatchers.Default) {
repeat(10) { repeatTime ->
delay(1000L)
println("job: $repeatTime")
}
}
delay(3500L) // 3.5초 대기
longJob.cancel() // 코루틴 취소
}
cancel 함수를 사용하면 실행 중인 코루틴을 취소할 수 있다. 하지만 cancel의 대상이 된 Job 객체는 곧바로 취소되는 것이 아니라 미래의 어느 시점에 취소되기 때문에, 코루틴이 실제로 취소되었는지 확인이 필요할 수 있다.
2) cancelAndJoin() 사용
cancelAndJoin 함수는 cancel과 join을 순차적으로 실행하는 함수다. 이 함수는 코루틴 취소를 요청하고 해당 코루틴이 실제로 취소되어 종료될 때까지 대기한다.
cancel 함수를 호출한 후 곧바로 다른 작업을 실행하면 해당 작업은 코루틴이 취소되기 전에 실행될 수 있기 때문에, 코루틴이 완전히 취소된 후에 작업을 실행하고 싶다면 cancelAndJoin 함수를 사용해야 한다.
fun main() = runBlocking<Unit> {
val longJob: Job = launch(Dispatchers.Default) {
// 작업 실행
}
longJob.cancelAndJoin() // longJob이 취소될 때까지 runBlocking 코루틴 일시 중단
executeAfterJobCancelled()
}
cancelAndJoin 함수를 호출하면 대상 코루틴의 취소가 완료될 때까지 호출부의 코루틴(runBlocking)이 일시 중단된다. 따라서 위 예제에서는 longJob 코루틴이 취소된 이후 executeAfterJobCancelled 함수가 실행되는 것이 보장된다.
5. 코루틴의 취소 확인
앞서 나온 cancel과 cancelAndJoin 함수를 사용해도 코루틴이 즉시 취소되는 것이 아니고, Job 객체 내부에 있는 취소 확인용 플래그를 바꾸기만 하며, 코루틴이 이 플래그를 확인하는 시점에 비로소 취소된다. 코루틴이 취소를 확인하는 시점은 일반적으로 일시 중단 지점이나 코루틴이 실행을 대기하는 시점이며, 이 시점들이 없다면 코루틴은 취소되지 않는다.
1) delay
fun main() = runBlocking<Unit> {
val whileJob = launch(Dispatchers.Default) {
while(true) {
println("작업 중")
delay(1L) // 취소 확인 지점
}
}
delay(100L)
whileJob.cancel()
}
delay 함수는 일시 중단 함수로, 특정 시간만큼 호출부의 코루틴을 일시 중단한다. 코루틴은 일시 중단되는 시점에 취소 여부를 확인하기 때문에 delay 함수를 통해 코루틴 취소를 확인할 수 있다.
하지만 이 방법은 while문이 반복될 때마다 작업을 강제로 1밀리초 동안 일시 중단시켜 성능 저하가 일어난다는 단점이 있다.
2) yield
fun main() = runBlocking<Unit> {
val whileJob = launch(Dispatchers.Default) {
while(true) {
println("작업 중")
yield()
}
}
delay(100L)
whileJob.cancel()
}
yield 함수가 호출되면 코루틴은 자신이 사용하던 스레드 사용을 중단하며, 이 시점에 코루틴이 취소됐는지 확인된다.
위 예제에서 Dispatchers.Default를 사용하는 코루틴은 whileJob밖에 없으므로 whileJob 코루틴은 yield를 호출하면 일시 중단 후 곧바로 재개되지만 잠깐 일시 중단된 시점에 취소 체크가 일어나 100밀리초 정도 후에 코루틴이 정상적으로 취소된다.
하지만 delay와 마찬가지로 while 문을 돌 때마다 스레드 사용이 양보되면서 일시 중단되는 문제가 있다.
3) CoroutineScope.isActive
fun main() = runBlocking<Unit> {
val whileJob = launch(Dispatchers.Default) {
while(this.isActive) {
println("작업 중")
}
}
delay(100L)
whileJob.cancel()
}
CoroutineScope는 코루틴이 활성화됐는지 확인할 수 있는 Boolean 타입의 isActive 프로퍼티를 제공한다. 코루틴에 취소가 요청되면 isActive 값이 false로 바뀌며, while 문의 인자로 this.isActive를 넘겨 코루틴이 취소 요청될 때 while 문이 종료되도록 만들 수 있다.
이 방법은 코루틴이 잠시 멈추지도 않고 스레드 사용을 양보하지도 않기 때문에 가장 효율적이다.
6. 코루틴의 상태와 Job의 상태 변수
1️⃣ 생성
이 상태는 코루틴이 생성만 되고 실행되지 않은 상태를 말한다.
코루틴 빌더를 통해 코루틴을 생성하면 기본적으로 생성 상태에 놓였다가 자동으로 실행 중 상태로 넘어간다. 만약 생성 상태에서 실행 중 상태로 자동 변경되지 않도록 하고 싶다면 코루틴 빌더의 start 인자로 CoroutineStart.Lazy를 넘겨 지연 시작이 적용된 코루틴을 생성해야 한다.
2️⃣ 실행 중
코루틴 빌더로 지연 코루틴이 아닌 코루틴을 만들면 자동으로 CoroutineDispatcher에 의해 스레드로 보내져 실행된다. 코루틴이 실제 실행 중일 때뿐만 아니라 실행된 후 일시 중단된 때도 해당 상태로 본다.
3️⃣ 실행 완료
실행 중인 코루틴이 모두 정상적으로 실행돼 실행 완료된 상태이다.
4️⃣ 취소 중
Job.cancel() 등을 통해 코루틴에 취소가 요청됐을 경우이다. 아직 취소된 상태가 아니기 때문에 코루틴은 계속해서 실행된다.
취소가 요청되면 실제로 코드가 실행 중이더라도 코루틴이 활성화된 상태로 보지 않는다.(isActive = false)
5️⃣ 취소 완료
코루틴의 취소 확인 시점(일시 중단 등)에 취소가 확인된 경우 취소 완료 상태가 된다. 이 상태에서 코루틴은 더 이상 실행되지 않는다.
코루틴 상태 | isActive | isCancelled | isCompleted |
생성(New) | false | false | false |
실행 중(Active) | true | false | false |
실행 완료(Completed) | false | false | true |
취소 중(Cancelling) | false | true | false |
취소 완료(Cancelled) | false | true | true |
'Study' 카테고리의 다른 글
[코틀린 코루틴의 정석] 멀티 스레드 vs 코루틴 (0) | 2025.01.05 |
---|---|
[오픈소스] Spring Boot 프로젝트 컨트리뷰터 되기 (0) | 2024.04.29 |
[Java] Method Area는 Heap 영역이 아니다! (1) | 2024.02.19 |
[Java] 자바 런타임 데이터 영역(Runtime Data Area) (0) | 2024.02.19 |
[Java] 변수와 객체 데이터 저장 (0) | 2024.02.17 |