관리 메뉴

너와 나의 스토리

[Coroutine] Async vs launch / Deffered vs Job / CoroutineContext와 Dispatcher / 일시중단함수 본문

Programming Language/Kotlin

[Coroutine] Async vs launch / Deffered vs Job / CoroutineContext와 Dispatcher / 일시중단함수

노는게제일좋아! 2022. 10. 18. 21:02
반응형

kotlinx.coroutines.CoroutineScope.async

  • async는 사실상 launch와 같은 일을 한다.
  • 유일한 차이는 launch가 Job을 반환하는 반면 async는 Deffered를 반환한다는 점뿐이다. 
    • 심지어 Deffered는 Job을 상속한 클래스이기 때문에 launch 대신 async를 사용해도 아무 문제 없다.
  • async는 코드 블록을 비동기로 실행할 수 있다.
    • async가 반환하는 Deffered의 await을 사용해서 코루틴이 결과 값을 내놓을 때까지 기다렸다가 결과값을 얻어낼 수 있다.

 

Deffered vs Job

  • Job
    • 타입 파라미터가 없음
  • Deffered
    • 타입 파라미터가 있는 제네릭 타입
    • Deffered 안에는 await() 함수가 정의되어 있다.
    • Deffered의 타입 파라미터는 Deffered 코루틴이 계산을 하고 돌려주는 값의 타입이다.

 

예제

  • async / await을 사용해서 1부터 3까지 수를 더해보자.
fun sumAll() {
    runBlocking {
        val cur = Instant.now()
        val d1 = async {
            delay(1000L);
            1
        }
        log("after async(d1)")
        val d2 = async {
            delay(2000L);
            2
        }
        log("after async(d2)")
        val d3 = async {
            delay(3000L);
            3
        }
        log("after async(d3)")

        log("1+2+3 = ${d1.await() + d2.await() + d3.await()}")
        val  duration = Duration.between(cur, Instant.now())
        log("after await all & add - duration: $duration")
    }
}

fun main() {
    log("main() started.")
    sumAll()
}

  • 병렬로 작동하여 위 작업을 하는 동안 총 3초정도 걸린 것을 확인할 수 있다.
    • PT prefix 의미: Period of Time
  • 그럼에도 불구하고 스레드를 여럿 사용하는 병렬 처리와 달리 모든 async 함수들이 메인 스레드 안에서 실행됨을 볼 수 있다.

 

CoroutineContext와 Dispatcher

  • launch, async 등은 모두 CoroutineScope의 확장 함수다. 
  • CoroutineScope에는 CoroutineContext 타입의 필드 하나만 들어있다. 
    • CoroutineScope는 CoroutineContext 필드를 launch 등의 확장 함수 내부에서 사용하기 위해 매개체 역할만을 담당한다.
  • CoroutineContext는 실제로 코루틴이 실행 중인 여러 작업(Job)과 디스패처(Dispatcher)를 저장하는 일종의 맵이라고 할 수 있다.
  • 코틀린 런타임은 이 CoroutineContext를 사용해서 다음에 실행할 작업을 선정하고, 어떻게 스레드에 배정할지에 대한 방법을 결정한다.
fun coroutineTest(){
    runBlocking {
        launch{ // 부모 context 사용
            log("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined){ // 특정 스레드에 종속되지 않음
            log("Unconfined : I'm working in thread ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Default){ // 특정 스레드에 종속되지 않음
            log("Default : I'm working in thread ${Thread.currentThread().name}")
        }
        launch(newSingleThreadContext("MyOwnThread")){ // 새 스레드를 사용
            log("newSingleThreadContext : I'm working in thread ${Thread.currentThread().name}")
        }
    }
}

fun main() {
    log("main() started.")
    coroutineTest()
}

  • 같은 launch를 사용하더라도 전달하는 컨텍스트에 따라 서로 다른 스레드상에서 코루틴이 실행됨을 알 수 있다.

 

 

코루틴 빌더

  • Kotlinx-couroutines-core 모듈이 제공하는 코루틴 빌더
    • launch
    • async
    • runBlocking
    • produce
      • 정해진 채널로 데이터를 스트림으로 보내는 코루틴을 만든다.
    • actor
      • 정해진 채널로 메시지를 받아 처리하는 액터를 코루틴으로 만든다.
  • 코루틴 빌더: 코루틴을 만들어주는 함수.

 

일시 중단 함수

  • Kotlinx-couroutines-core 모듈의 최상위에 정의된 일시 중단 함수
  • delay()
  • yield()
  • withContext
    • 다른 컨텍스트로 코루틴을 전환.
  • withTimeout
    • 코루틴이 정해진 시간 안에 실행되지 않으면 예외를 발생시키게 함.
  • withTimeutOrNull
    • 코루틴이 정해진 시간 안에 실행되지 않으면 null을 결과로 돌려준다.
  • awaitAll
    • 모든 작업의 성공을 기다린다. 작업 중 어느 하나가 예외로 실패하면 awaitAll도 그 예외로 실패한다.
  • joinAll
    • 모든 작업이 끝날 때까지 현재 작업을 일시 중단시킨다.

 

suspend 키워드와 코틀린의 일시 중단 함수 컴파일 방법

  • 코루틴이 아닌 일반 함수 속에서 delay()나 yield()를 쓰면 컴파일 수준에서 사용이 금지된다.
  • 코틀린은 코루틴 지원을 위해 suspend라는 키워드를 지원한다.
    • 함수 정의의 fun 앞에 suspend를 넣으면 일시 중단 함수를 만들 수 있다.
suspend fun yieldThreeTimes() {
    log("1")
    delay(1000L)
    yield()
    log("2")
    delay(1000L)
    yield()
    log("3")
}

fun suspendExample() {
    GlobalScope.launch { yieldThreeTimes() }
}

 

반응형
Comments