[Kotlin] Coroutine 살짝쿵 더 맛보기

H43RO·2021년 8월 20일
6

Kotlin 과 친해지기

목록 보기
5/18
post-thumbnail

💡 코틀린 공식 문서를 참고하여 작성한 글입니다 - Coroutines basics | Kotlin

이전 포스팅과 이어집니다! [Kotlin] Coroutine 첫 인상 살펴보기

코루틴 동작을 함수로 따로 빼보기

이전 포스팅에서, 다음과 같은 코드를 통해 코루틴을 접해보았다. 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 를 활용하여, 다른 빌더들이 제공하는 코루틴 스코프 외에 따로 자신만의 코루틴 스코프를 선언할 수도 있다. 이를 이용하여 코루틴 스코프를 생성하게 되면, 이는 스코프 내의 작업들이 모두 끝날 때 까지 종료되지 않는다.

runBlockingcoroutineScope겉으로 보기에 뭔 차이인가 싶다. 둘다 스코프 내의 작업들이 모두 끝날 때 까지 종료되지 않는 다는 점에선 다른 것이 없기 때문이다. 하지만, 이 둘은 완전히 다르다.

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!

동시성을 보장하는 Scope Builder

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 객체는 코루틴을 제어하기 위한 다양한 기능들을 제공한다.


이전 포스팅에 이어서 코루틴에 대해서 기본적인 개념, 사용법 등을 알아보았다. 다음 포스팅부터는 조금 더 본격적으로, 자세하게 코루틴에 대해 알아보고자 한다. 만약 지금까지의 포스팅이 흥미로웠다면 한 번 코루틴을 공부해보는 것을 추천한다!

profile
어려울수록 기본에 미치고 열광하라

0개의 댓글