
지난 시간에 이어 코루틴의 정석이라는 책을 읽으며 코루틴을 학습하려고 한다. 책을 읽으며 핵심을 정리하고 그 외 의문들을 확장해 나갈 것이다.
(구매처 : 교보문고 사이트 )

해당 책에서는 인텔리제이로 코틀린을 실습하기 때문에 인텔리제이에 다음처럼 프로젝트를 생성하였다.
coroutinesGradleKotlin17 (없다면 설치 필요)
코루틴 라이브러리 추가를 위해 그레이들 파일에 추가한다.
build.gradle.kts 의 dependencies 와 kotlin 블럭이 다음과 같아야한다.
해당 작업은 kotlinx.coroutines 라이브러리를 추가하는 것이다
(사실 기본적으로 코틀린은 언어 레벨에서 코루틴을 지원하지만 저수준 API만을 지원하기 때문에 실제 개발에 필요한 고수준 API는 코루틴 라이브러리를 통해 제공받을 수 있다)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2")
}
kotlin {
jvmToolchain(17) // jvmToolchain 의 jdkVersion 17로 변경
}
가장 먼저 작성할 수 있는 기본 예제는 아래와 같다.
import kotlinx.coroutines.runBlocking
fun main() = runBlocking<Unit> {
println("Hello Coroutines")
}
runBlocking은 이름 그대로 스레드를 블로킹(blocking) 하면서 코루틴을 시작한다.
즉, main 함수 안에서 코루틴 스코프를 만들어 내부 블록이 끝날 때까지 메인 스레드를 멈추고 기다린다.
-> 그래서 main 진입점에서 주로 사용
그래서
runBlocking을 테스트나 예제 코드에서는 유용하지만 실제 안드로이드 UI에서는 잘 쓰이지 않는다. 주로main진입점이나 단위 테스트에서 코루틴을 실행하기 위한 진입점으로 사용한다.
코루틴은 단독으로 존재하지 않고, 항상 Coroutine Scope(실행 환경) 안에서 실행된다.
스코프는 간단히 말해 코루틴의 생명주기 + 실행 문맥(Context) 을 정의하는 공간이다.
코루틴은 runBlocking, launch, async 같은 코루틴 빌더를 통해 생성된다.
runBlocking 는 현재 스레드를 블로킹하면서 코루틴 스코프를 만든다.
launch 는 반환값이 없는 코루틴을 실행한다. 결과 대신 Job 이라는 객체를 반환한다.
async는 반환값이 있는 코루틴을 실행한다. 결과는 Deferred<T>로 감싸지며 await() 호출 시 값을 얻는다.
아래와 같은 코드가 있다고 하자. 코루틴은 몇 개일까?
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking<Unit> {
println("[${Thread.currentThread().name}] 실행")
launch {
println("[${Thread.currentThread().name}] 실행")
}
launch {
println("[${Thread.currentThread().name}] 실행")
}
}
우선 runBlocking 자체가 하나의 코루틴을 만든다.
그리고 그 내부에서 launch {} 호출 때마다 새로운 코루틴들을 만든다.
따라서 3개의 코루틴이 생성된다.
실행 결과는 다음과 같다.
[main @coroutine#1] 실행
[main @coroutine#2] 실행
[main @coroutine#3] 실행
여기서 기억해야할 것은
Thread.currentThread().name을 통해 현재 실행 중인 스레드의 이름을 출력할 수 있다. 만약 내 출력처럼 코루틴의 이름(@coroutin#1 등)도 출력하려면 Main.kt의Edit Configure에서 JVM의 VM option에-Dkotlinx.coroutines.debug를 추가하면 스레드의 이름 출력 시 코루틴의 이름을 같이 출력할 수 있다.
launch와 async의 차이
launch와 async는 둘 다 새 코루틴을 만들고 (부모 스코프의 자식), 둘 다 구조적 동시성 규칙을 따른다. (부모가 끝나면 자식의 완료/취소 보장됨)
차이는launch는 결과가 없는 작업(fire-and-forget)이고async는 결과를 계산해서 반환한다.launch는Job이라는 객체를 반환 타입으로 하고,async는Deferred<T>라는 반환 타입을 가진다. 만약 내가 UI 업데이트/로깅 등 "반환값이 불필요"한 경우에는launch를 실행하고, 동시에 여러 API 콜 후 결과를 합치고 싶을 때는async + await()를 사용한다.
runBlocking
main 함수 진입점이나 테스트에서 코루틴을 실행하기 위해 사용. 내부 블록이 끝날 때까지 스레드를 블로킹한다.
Coroutine Scope
Coroutine Builder
runBlocking: 현재 스레드를 블로킹하면서 스코프 생성.launch: 결과 없는 작업 실행, Job 반환. fire-and-forget 용도.async: 값을 반환하는 작업 실행, Deferred<T> 반환. await()으로 결과 획득.launch vs async
launch: UI 업데이트, 로그 기록, 캐시 저장 등 → 반환값 필요 없는 작업.async: 여러 API 결과를 합치거나 병렬 연산을 수행할 때 사용.runBlocking 은 어떤 상황에서 주로 사용되는가?
Coroutine Scope의 주요 역할 3가지를 설명하라.
아래 코드에서 코루틴은 총 몇 개 생성되는가?
fun main() = runBlocking {
launch { println("A") }
launch { println("B") }
}
launch 와 async 의 차이점을 반환값과 예외 처리 관점에서 설명하라.
UI 이벤트 트래킹, 캐시 저장, 여러 API 동시 호출 → 각각 launch/async 중 어떤 것을 쓰는 게 적합한가?
runBlocking 은 어떤 상황에서 주로 사용되는가?
→ main 함수 진입점이나 테스트 코드에서 코루틴을 실행할 때 사용한다. 실제 안드로이드 UI에서는 스레드를 블로킹하므로 사용하지 않는다.
Coroutine Scope의 주요 역할 3가지
코루틴 개수
→ runBlocking 1개 + launch 2개 = 총 3개의 코루틴이 생성된다.
launch vs async 차이 (반환값 & 예외 처리)
launch: 반환값 없음, Job 반환. 예외 발생 시 즉시 부모로 전파.async: 결과를 반환(Deferred<T>), await() 호출 시 예외가 재던져진다.상황별 적합한 선택
launchlaunchasync