💡 코틀린 공식 문서를 참고하여 작성한 글입니다 - Coroutines basics | Kotlin
이전 포스팅에서, 다음과 같은 코드를 통해 코루틴을 접해보았다. runBlocking
키워드, launch
키워드, 그리고 delay
키워드 등을 알아봤었다. 이 때 launch
키워드는 새로운 코루틴 객체를 생성하고, 해당 코루틴의 동작을 수행하는 역할을 한다고 했었다. 예제를 다시 갖고 와보았다.
fun main() = runBlocking { // Coroutine Scope
launch { // 새로운 코루틴 객체를 생성 & 해당 코루틴 동작 시작
delay(1000L) // Non-blocking 1초 딜레이 커맨드
println("World!") // 1초 후에 출력됨!
}
println("Hello!") // Main Coroutine 은 delay 되지 않고 이어서 수행됨
}
// [실행결과]
// Hello
// World!
그럼 한 번, launch
스코프 내에 정의되어 있는 코루틴 작업을 함수로 따로 빼보자.
바로 suspend
라는 키워드를 사용하면 된다!
fun main() = runBlocking { // Coroutine Scope
launch { // 새로운 코루틴 객체를 생성 & 해당 코루틴 동작 시작
doWorld()
}
println("Hello") // Main Coroutine 은 delay 되지 않고 이어서 수행됨
}
suspend fun doWorld() {
delay(1000L) // Non-blocking 1초 딜레이 커맨드
println("World!") // 1초 후에 출력됨!
}
suspend
함수는 일반적인 함수들처럼 코루틴 내부에서 사용할 수 있을 뿐만 아니라, 코루틴 스코프 내에서만 선언 가능한 Suspending Function (일시중단 가능한 함수) 들, 즉 delay
같은 녀석들을 사용할 수 있다는 특징이 있다.
coroutinScope
라는 Coroutine Scope Builder 를 활용하여, 다른 빌더들이 제공하는 코루틴 스코프 외에 따로 자신만의 코루틴 스코프를 선언할 수도 있다. 이를 이용하여 코루틴 스코프를 생성하게 되면, 이는 스코프 내의 작업들이 모두 끝날 때 까지 종료되지 않는다.
runBlocking
과coroutineScope
는 겉으로 보기에 뭔 차이인가 싶다. 둘다 스코프 내의 작업들이 모두 끝날 때 까지 종료되지 않는 다는 점에선 다른 것이 없기 때문이다. 하지만, 이 둘은 완전히 다르다.
runBlocking
스코프는 해당 코루틴 객체가 속해있는 쓰레드를 블로킹해버리고,coroutineScope
의 경우에는 그저 해당 코루틴 작업이 일시중단이 되는 것 뿐, 쓰레드는 이외의 다른 작업을 수행할 수 있다.
이러한 차이점 때문에 runBlocking
은 우리가 아는 Regular Function (일반적인 함수) 라 부르고, coroutineScope
의 경우엔 Suspending Function (일시중단 가능한 함수) 라고 부른다.
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
// [실행 결과]
// Hello
// World!
coroutineScope
내에는 여러 개의 코루틴 객체를 넣을 수 있다. 즉, 그 어떤 Suspending Function 도 동시성을 보장하며 실행된다는 뜻이다. 아래 예제 코드와, 실행 결과를 살펴보자.
// doWorld() 와 "Done" 출력이 순차적으로 진행됨
fun main() = runBlocking {
doWorld()
println("Done")
}
// launch 를 통해 선언한 두 코루틴 작업이 동시에 진행됨
suspend fun doWorld() = coroutineScope {
launch {
delay(2000L)
println("World 2") // 2초 Delay 이후 실행 : 3 빠따
}
launch {
delay(1000L)
println("World 1") // 1초 Delay 이후 실행 : 2 빠따
}
println("Hello") // 앞의 작업들이 모두 Delay 되었으므로 메인 코루틴 작업이므로 1 빠따로 실행
}
// [실행 결과]
// Hello
// World 1
// World 2
// Done
runBlocking
스코프 내에서는 별다른 동시성 처리가 없기 때문에 doWorld()
가 실행된 후, "Done" 을 출력하도록 코드가 짜여져있다 (순차적). doWorld()
스코프 내부에는, 두 가지 코루틴 작업들이 있으며 이들은 완전히 동시성이 보장되기 때문에, Suspending Function 의 제어에 따라 알맞는 출력 결과를 뱉는다.
Job
객체launch
라는 Coroutine Builder 는 Job
이라는 객체를 반환한다. Job
객체는 다양한 것을 할 수 있는데, 예를들어 코루틴 동작을 실행하거나, 현재 동작 여부 확인, 혹은 해당 코루틴 동작이 완료될 때 까지 끝까지 대기할 필요가 있을 때 사용된다. 말 그대로 Job
은, 코루틴을 제어할 수 있는 객체이다.
fun main() = runBlocking {
val job = launch {
delay(1000L)
println("World!")
}
println("Hello")
job.join() // 스코프 내의 모든 코루틴 동작이 끝날 때 까지 대기
println("Done")
}
// [실행 결과]
// Hello
// World!
// Done
실행 결과를 보면, Job
객체의 join()
메소드를 통해 해당 스코프의 코루틴 동작들을 모두 수행하고 난 뒤에 "Done" 이 출력되는 것을 알 수 있다. join()
메소드는, 코루틴을 시작하며 스코프 내의 모든 코루틴 동작이 끝날 때 까지 대기하게끔 하는 역할을 한다. 이 외에도 Job
객체는 코루틴을 제어하기 위한 다양한 기능들을 제공한다.
이전 포스팅에 이어서 코루틴에 대해서 기본적인 개념, 사용법 등을 알아보았다. 다음 포스팅부터는 조금 더 본격적으로, 자세하게 코루틴에 대해 알아보고자 한다. 만약 지금까지의 포스팅이 흥미로웠다면 한 번 코루틴을 공부해보는 것을 추천한다!