개요
4장 코루틴 빌더와 Job에 대한 내용을 정리했다.
1. 코루틴 빌더 및 Job 설명
코루틴 빌더는 코루틴을 생성하는 함수로, runBlocking, launch 등이 있다.
이 코루틴 빌더 함수는 코루틴을 생성하고 코루틴을 추상화한 Job 객체라는 것을 반환한다.
이 Job객체는 코루틴의 상태를 추적하고 제어하는 데 사용된다.
fun main() = runBlocking {
val job: Job = launch(Dispatchers.IO) {
println("${Thread.currentThread().name 적용}")
}
}
이 Job 객체를 사용해서 코루틴을 제어하고 조작하는 방법에 대해 정리해보겠다!
2. join을 통한 코루틴 순차 처리
코루틴들 사이에서도 순차 처리가 필요한 경우가 발생할 수 있다.
***나의 경우 jwt 토큰을 통해 서버와 통신할 때 주로 필요했다.
로그인 한뒤 액세스 토큰을 로컬 DB에 저장하고 이를 활용해 자동 로그인을 구현했었는데, 이 과정에서 사용자 로그인 검증한 뒤 서버로부터 받아온 액세스 토큰과 리프레쉬 토큰을 저장한뒤 화면을 넘겨줘야 했다.
이때 순차 처리가 이뤄지지 않는다면, 로컬에 저장되기 전에 바로 넘어가서 다른 API들 요청이 불가능했다.***
fun main() = runBlocking {
val getTokenJob = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 클릭")
delay(100L)
println("${Thread.currentThread().name} 토큰 저장")
}
val successLogin = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 성공")
}
}
위 코드를 실행했을 때, 서버에 응답받고 토큰 저장하기 전에 로그인 성공되어 메인 화면으로 넘어가게 된다.
이 문제점을 해결하기 위해서는 getTokenJob이 완료된 후에 successLogin이 실행되어야 한다.
이때 Job 객체는 순차 처리할 수 있도록 join()이라는 함수를 제공한다.
2-1. join() 적용
만약 JobA 코루틴이 완료된 후에 JobB 코루틴이 실행돼야 하면 JobB 코루틴이 실행되기 전 JobA 코루틴에 join 함수를 호출하면 된다.
fun main() = runBlocking {
val getTokenJob = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 클릭")
delay(100L)
println("${Thread.currentThread().name} 토큰 저장")
}
getTokenJob.join()
val successLogin = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 성공")
}
}
맨 위 코드에서 getTokenjob.join()을 넣어줬더니 getTokenjob 코루틴의 작업이 완료될 때 까지 join을 호출한 코루틴이 일시 중단되게 된다.
즉, runBlocking 코루틴이 getTokenjob.join()을 호출하면 runBlocking 코루틴은 updateTokenJob 코루틴이 완료될 때까지 일시 중단된다.
이후에 getTokenjob이 완료되면 runBlocking 코루틴이 재개되며, successLogin을 실행하게 된다.
여기서 알고가야 할 점은, join 함수는 일시 중단이 가능한 지점(코루틴 등)에서만 호출될 수 있다.
주의점
그렇다면 만약, join 함수가 호출되기 전에 다른 코루틴이 있다면 어떻게 될까?
fun main() = runBlocking {
val getTokenJob = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 클릭")
delay(100L)
println("${Thread.currentThread().name} 토큰 저장")
}
val independentJob = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 독립적인 작업")
}
getTokenJob.join()
val successLogin = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 로그인 성공")
}
}
위의 코드는 getTokenJob.join()을 호출하기 전에 independentJob이라는 추가된 코루틴이 실행되게 된다.
2-2. joinAll 사용하기
실제 개발 할 때는 여러 코루틴을 병렬로 실행한 후에 실행한 요청들이 모두 완료될 때까지 기다리고 다음 작업을 하는 것이 효율적이라 한다.
여기서 예를 든게, SNS 앱의 이미지 업로드 기능이다. 복수의 이미지를 업로드해야 할 상황에서 개별 이미지를 모두 변환한 후에 업로드 작업을 진행하도록 만드는 것이 성능이 좋을 것이다.
이때 사용하는 것이 joinAll()이라는 함수다.
public suspend fun joinAll(varargs jobs: job) : Unit = jobs.forEach {
it.join()
}
forEach를 통해 대상이 된 코루틴에 일일이 join() 적용해주고 있다.
fun main() = runBlocking {
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 = launch(Dispatchers.IO) {
println("${Thread.currentThread().name} 이미지 1,2 업로드")
}
}
이미지 변환1,2 작업을 완료 한 후에 이미지 1,2를 업로드 하도록 joinAll() 함수를 적용했다.
2-3. CoroutineStart.LAZY로 코루틴 지연 시작
지금까지 launch 함수를 사용해서 코루틴을 생성한다면 사용 가능한 스레드가 있는 경우 즉시 실행된다.
코루틴을 미리 생성하고 나중에 실행되도록 할 수 있다. 이게 필요한 상황이 내가 생각했을 때는 앱을 실행하고 API 요청을 바로 보내는 것이 아닌, 사용자가 특정 버튼을 눌렀을 때 데이터를 가져오도록 하는 상황?
이 지연 코루틴을 생성하기 위해서는 launch 함수의 start 인자로 CoroutineStart.LAZY를 넘겨줘야 한다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val lazyJob : Job = launch(start = CoroutineStart.LAZY) {
println("${getElapsedTimeStart(startTime)} 지연 실행")
}
delay(1000L) //1초 대기
lazyJob.start()
}
fun getElapsedTimeStart(startTime : Long) : String {
return "시간 변화 ${System.currentTimeMillis() - startTime}ms"
}
그 뒤, 코루틴을 실행시키고자 할때 Job 객체에 start()함수를 호출하면 된다.
지연 코루틴은 생성 후 자동으로 실행되는 것이 아니라, 직접 실행을 요청해야 한다.
다음 글에 코루틴 취소 및 상태에 대해 정리해보겠다.
'Android > Coroutine' 카테고리의 다른 글
[Coroutine] Coroutine Dispatcher (0) | 2025.03.04 |
---|---|
[Coroutine] 코루틴이란? (0) | 2025.03.04 |