반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[코틀린 코루틴] 6장. 코루틴 빌더 본문

Kotlin/코틀린 코루틴

[코틀린 코루틴] 6장. 코루틴 빌더

어리둥절범고래 2024. 3. 24. 18:18
반응형

일반 함수가 중단 함수를 호출하는 것은 불가능합니다.

중단 함수를 연속으로 호출하면 그 시작점은 코루틴 빌더로, 일반 함수와 중단 가능한 세계를 연결해줍니다.

코루틴 빌더는 크게 세 가지가 있습니다.

 

  • launch
  • runBlocking
  • async

 

 

launch 빌더

 

laucn는 새로운 스레드를 시작하는 것과 비슷하며, 작동하는 방식은 데몬 스레드와 어느 정도 비슷하지만 훨씬 가볍습니다.

launch 함수는 CoroutineScope 인터페이스의 확장 함수이며, CoroutineScope 인터페이스는 구조화된 동시성의 핵심입니다.

다음은 launch 함수를 사용한 예제입니다.

fun main() {
    GlobalScope.launch {
        delay(1000L)
        println("World!!")
    }
    GlobalScope.launch {
        delay(1000L)
        println("World!!")
    }
    GlobalScope.launch {
        delay(1000L)
        println("World!!")
    }
    println("Hello,")
    Thread.sleep(2000L)
}
//Hello,
//World!!
//World!!
//World!!

 

Thread.sleep을 호출한 이유는, delay가 스레드를 블록시키지 않고 코루틴을 중단하기 때문에 스레드를 잠들게 하여 코루틴이 실행할 시간을 준 것입니다.

스레드가 블로킹되지 않으면 할 일이 없어져 그대로 종료되고 맙니다.

 

 

 

runBlocking 빌더

 

코루틴이 스레드를 블로킹하지 않는 것이 일반적이지만, 블로킹이 필요한 경우에 사용할 수 있는 빌더입니다.

runBlocking 빌더는 시작한 스레드를 중단시킵니다.

runBlocking 빌더가 사용되는 특수한 경우는 두 가지입니다.

 

  • 프로그램이 끝나는 걸 방지하기 위해 스레드를 블로킹할 필요가 있는 메인 함수일 때
  • 스레드를 블로킹할 필요가 있는 유닛 테스트
fun main() = runBlocking { 
    // ...
}

class MyTests {
    @Test
    fun `a test`() = runBlocking { 
        
    }
}

 

 

 

async 빌더

 

async 코루틴 빌더는 lauch와 비슷하지만 값을 생성하며, 이 값은 람다 표현식에 의해 반환되어야 합니다.

async 함수는 Deferred<T> 타입의 객체를 리턴하며, 여기서 T는 생성되는 값의 타입입니다.

Deferred에는 작업이 끝나면 값을 반환하는 중단 함수인 await가 있습니다.

launch 빌더와 비슷하게 async 빌더는 호출되자마자 코루틴을 즉시 시작합니다.

따라서 몇 개의 작업을 한번에 시작하고 모든 결과를 한껍너에 기다릴 때 사용합니다.

Deferred는 값이 생성되면 해당 값을 내부에 저장하기 때문에 await에서 값이 반환되는 즉시 사용할 수 있습니다.

값이 생성되기 전에 await를 호출하면 값이 나올 때까지 기다립니다.

fun main() = runBlocking {
    val res1 = GlobalScope.async {
        delay(1000)
        "Text 1"
    }
    val res2 = GlobalScope.async {
        delay(3000)
        "Text 2"
    }
    val res3 = GlobalScope.async {
        delay(2000)
        "Text 3"
    }
    println(res1.await())
    println(res2.await())
    println(res3.await())
}
//1초 후
//Text 1
//3초 후
//Text 2
//Text 3

async 빌더는 두 가지 다른 곳에서 데이터를 얻어와 합치는 경우처럼, 두 작업을 병렬로 실행할 때 주로 사용합니다.

scope.launch { 
    val news = async { 
        newsRepo.getNews()
            .sortedByDescending { it.date }
    }
    val newsSummary = newsRepo.getNewsSummary()
    view.showNews(
        newsSummary,
        news.await()
    )
}

 

 

 

구조화된 동시성

 

세 빌더들의 정의를 살펴보면 block 파라미터가 확장 함수 형태의 함수타입이며, 리시버 객체가 CoroutineScope인 것을 확인할 수 있습니다.

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {

이를 통해 부모가 되는 코루틴 빌더의 스코프 안에서 리시버를 통해 this.launch 또는 launch와 같이 호출할 수 있습니다.

블록 안에서 리시버 객체가 CoroutinScope인 것을 확인할 수 있다.

이를 통해 구조화된 동시성이라는 관계가 성립합니다.

부모-자식 관계의 가장 중요한 특징은 다음과 같습니다.

 

  • 자식은 부모로부터 컨텍스트를 상속받는다.
  • 부모는 모든 자식이 작업을 마칠 때까지 기다린다.
  • 부모 코루틴이 취소되면 자식 코루틴도 취소된다.
  • 자식 코루틴에서 에러가 발생하면, 부모 코루틴 또한 에러로 소멸한다.

다른 코루틴 빌더와 달리, runBlocking은 CoroutineScope의 확장 함수가 아닙니다.

runBlocking은 자식이 될 수 없으며 루트 코루틴으로만 사용될 수 있다는 것을 의미합니다.

 

 

 

coroutineScope 사용하기

 

중단 함수는 다른 중단 함수들로부터 호출되어야 하며, 모든 중단 함수는 코루틴 빌더로 시작되어야 합니다.

중단 함수 내에는 스코프가 없으며, 스코프를 인자로 넘기는 건 좋은 방법이 아닙니다.

대신 코루틴 빌더가 사용할 스코프를 만들어주는 중단 함수인 coroutineScope 함수를 사용하는 것이 바람직합니다.

 

리포지토리 함수에서 비동기적으로 두 개의 자원을 가져와야 하는 상황을 예로 들어보겠습니다.

suspend fun getArticlesForUser(
    userToken: String?
): List<ArticleJson> = coroutineScope { 
    val articles = async { articleRepository.getArticles() }
    val user = userService.getUser(userToken)
    articles.await()
        .filter { canSeeOnList(user, it) }
        .map { toArticleJson(it) }
}

coroutineScope는 람다 표현식이 필요로 하는 스코프를 만들어주는 중단 함수이며, 일반적으로 중단 함수 내에서 스코프가 필요할 때 사용합니다.

 

 


요약

 

  • 동시성을 처리하기 위해서는 함수를 coroutineScope로 래핑한 다음, 스코프 내에서 빌더를 사용하는 것이 좋다.
반응형