Gradle dependency 설정
코루틴을 사용하기 위해서는 다음 의존성을 설정해야 한다.
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
}
https://github.com/Kotlin/kotlinx.coroutines 에서 추가적인 의존성을 설정할 수 있다.
코루틴 만들기
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
delay(1000L)
print("World!")
}
print("Hello, ")
Thread.sleep(2000L)
}
launch
는 코루틴 빌더이며 이를 이용해 CoroutineScope에서 코드 블록을 실행시킬 수 있다.
코루틴 블록 내에서는 delay
가 쓰이지만, 코루틴 블록 밖에서는 Thread.sleep
이 실행되는 것을 볼 수 있는데, delay
는 코루틴 내에서 스레드를 block하지 않고 코루틴을 중단시키는 suspend function이기 때문이다.
Blocking과 Non-blocking
앞서 말했듯이 Thread.sleep는 코루틴이 아니기 때문에 스레드가 block 된다. 스레드가 block 되는 부분이 어디인지 혼동하기 쉽다. 이럴때 runBlocking
이라는 코루틴 빌더를 사용해 blocking을 명확하게 알 수 있다.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
delay(1000L)
print("World!")
}
print("Hello, ")
runBlocking {
delay(2000)
}
}
이제 non-blocking한 delay
함수만을 사용하며 runBlocking
을 호출하는 메인 스레드는 runBlocking
내부의 코루틴이 완료될 때까지 스레드가 block 된다.
runBlocking
안에서도 새로운 코루틴을 시작할 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking {
GlobalScope.launch {
delay(1000L)
print("World!")
}
print("Hello, ")
delay(2000L)
}
Job
지금까지의 예제는 다른 코루틴이 끝날 때까지 2초 딜레이를 주는 방법을 사용했다. 좋은 방법은 아니기 때문에 다른 코루틴이 끝날 때까지 Non-blocking한 방식으로 기다리게 할 수 있는 Job을 사용하는 것이 좋다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = GlobalScope.launch {
delay(1000L)
print("World!")
}
print("Hello, ")
job.join()
}
구조화된 동시성
위의 예제는 GlobalScope.launch
를 사용했다. GlobalScope는 최상위 범위에서 코루틴을 실행하는데, 이 코루틴의 생명주기는 애플리케이션의 생명주기를 따르게 되는데, 리스크가 크기 때문에 위험하다.
GlobalScope를 사용하는 대신 지금 작업하고 있는 범위 내에서 코루틴을 생성할 수 있다. runBlocking
launch
모두 block: suspend CoroutineScope.() -> T
시그니처로 코드 블록 내에서 this
로 scope에 접근할 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
print("World!")
}
print("Hello, ")
}
현재 작업 범위 내에서 코루틴을 생성하면 생성한 코루틴이 모두 끝날 때까지 기다리게 된다.
Scope builder
Builder가 제공하는 Coroutine Scope 외에도 coroutineScope
빌더를 사용하여 또 다른 scope를 생성할 수 있다.
coroutineScope
는 자신 블록 내부의 동작이 완료되기를 기다리는 것은 같으나 runBlocking
과는 다르게 현재 스레드는 block 하지 않는다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
문장은 다음과 같은 순서로 출력된다.
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
coroutineScope
를 사용하면 블록 내부의 동작이 완료되는 것을 기다리기 때문에 블록 뒤의 코드는 블록이 끝날 때까지 실행되지 않는다.
Suspend function
함수에 suspend 한정자를 붙여 함수가 중단과 재개가 가능하도록 할 수 있다. suspend 함수 내에서는 다른 suspend 함수를 실행시킬 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
print("Hello, ")
}
suspend fun doWorld() {
delay(1000L)
print("World!")
}