본문 바로가기
개발 이야기/안드로이드 개발

Kotlin의 Coroutine, 코루틴 #1

by 정선한 2023. 1. 31.
728x90
반응형

Co-Routine, Co(접/함께, 동시에) 코틀린의 코루틴은 무엇인지, 그리고 왜 사용되는지 알아보겠습니다. 안드로이드 가이드 문서를 살펴보면 비동기 프로그래밍을 하는 경우, Coroutine을 사용하도록 권장하고 있습니다.

공식문서에서는 suspendable computations라는 표현을 통하여 Coroutine에 대한 설명을 시작합니다. 중단시킬 수 있는 계산 정도로 해석하면 좋을 것 같은데, 이 표현 자체가 비동기 프로그래밍을 가장 적절하게 표현할 수 있다는 생각이 듭니다.

상기 그림과 같이 코루틴은 프로세스의 시작 이후 프로세스가 중단되었다가 그 이후 중단된 그 시점에 다시 재개될 수 있도록 코드를 구성할 수 있습니다. 이렇게 프로세스의 중간에 나가고 다시 들어올 때, return문이 없어도 언제든지 나가고 다시 해당 지점으로 들어올 수 있습니다.
이런 코루틴은 Thread에서 실행되는데, 하나의 Thread 내에서 여러 Coroutine들이 동작할 수 있고 Main Thread 이외에 Sub Thread가 존재하여 작업이 Non-blocking 한 형태로 여러 Coroutine이 동작할 수 있다.

참고자료 : 타 블로그에 이 Thread 작업에 대한 내용이 잘 정리 되어 있어 첨부하였습니다. / 이해가 쉽게 되는 글입니다.

아주 간단한 코드들을 활용해서 Kotlin에서의 Coroutine을 사용하는 방법을 알아보겠습니다. 주된 내용은 Kotlin Document에서 인용하였습니다.

fun main() = runBlocking {
	// this : CoroutineScope
    launch {
        delay(1000L)
    }
}

runBlocking : 현재 Thread에서 Coroutine을 사용하기 위한 Coroutine Builder입니다.
새 Coroutine을 실행하고 완료될 때까지 현재 Main Thread를 차단합니다. 이는 Coroutine에서 사용하지 않기를 권장하고 있습니다.

expect fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext, 
    block: suspend CoroutineScope.() -> T
): T

launch : 작동 중인 나머지 코드와 동시에 새로운 Coroutine을 시작합니다.
현재 Thread를 차단하지 않고 새 Coroutine을 시작하며 Coroutine에 대한 참조를 Job으로 반환합니다.

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job

delay : suspend function으로 Thread를 차단하지 않고 주어진 시간 동안 Coroutine을 지연하고 지정한 시간 후에 다시 시작합니다.

suspend fun delay ( timeMillis : Long )

fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {
    launch {
        delay(1000L)
    }
}

coroutineScope : coroutineScope를 만들고 해당 범위에 지정된 suspend block을 호출한다. 제공된 범위는 외부범위에서 coroutineContext를 상속하지만 context의 Job을 재정의 한다. 이 함수는 하위 coroutine이 실패하면 해당 범위 또한 실패하여 나머지 하위 항목이 모두 취소된다. 이 함수는 지정된 suspend block과 해당 block의 모든 하위 coroutine이 완료되는 즉시 반환된다.

suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() -> R
): R

supervisorScope : Supervisor Job으로 coroutineScope를 만들고 이 범위에서 지정된 suspend block을 호출한다. 제공된 범위는 외부범위에서 coroutineContext를 상속하지만 context의 Supervisor Job을 재정의 한다. 이 함수는 지정된 block과 모든 하위 coroutine이 완료되는 즉시 반환된다.
coroutineScope와의 차이점
하위 항목의 fail에 대하여 해당 범위의 fail로 간주하지 않으며 다른 하위 항목에 대한 영향을 주지 않기 때문에 하위 항목의 fail에 대한 사용자 지정 정책을 구현할 수 있다.

suspend fun <R> supervisorScope(
    block: suspend CoroutineScope.() -> R
): R

val job = launch {
    delay(1000L)
}

job.join()

job : 개념적으로 백그라운드 작업을 의미하며 job은 completion가 끝나는 life-cycle에서 취소가 가능하다. job은 parent-child의 계층으로 정렬할 수 있으며 parent 계층이 취소되면 child 계층도 취소된다. failure가 발생하는 CancellationException의 경우에는 즉시 parent 계층이 취소되며 다른 child 계층의 항목들도 취소된다. 해당 동작에 대하여 Supervisor Job을 이용하여 사용자 지정이 가능하다.

interface Job : CoroutineContext.Element

Coroutine Job은 coroutine builder를 통해 만들어지며 지정된 코드 블록을 실행시키고 해당 블록이 완료되면 Job도 완료된다.
Completable Job은 Job() 팩토리 함수로 생성된다. CompletableJob.complete를 호출하면 완료된다.
Job의 실행은 결과 값을 생성하지 않으며, 오로지 side-effects만을 위해 시작한다. Job에 대한 결과 실행은 다음 페이지를 참조.

wait children
    +-----+ start  +--------+ complete   +-------------+  finish  +-----------+
    | New | -----> | Active | ---------> | Completing  | -------> | Completed |
    +-----+        +--------+            +-------------+          +-----------+
                     |  cancel / fail       |
                     |     +----------------+
                     |     |
                     V     V
                 +------------+                           finish  +-----------+
                 | Cancelling | --------------------------------> | Cancelled |
                 +------------+                                   +-----------+
728x90
반응형