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!")
}

© 2021. All rights reserved.

Powered by Hydejack v9.1.6