# 코루틴과 Async/Await

- [Kotlin Docs](<https://kotlinlang.org/docs/coroutines-guide.html>)

- 코루틴
    - 비선점형 멀티태스킹을 수행하는 일반화한 서브루틴이다.
    - 코루틴은 실행을 일시 중단(suspend)하고 재개(resume)할 수 있는 여러 진입 지점(entry point)을 허용
        - 서브루틴 : 반복 호출할 수 있게 정의한 프로그램 구성 요소(함수라고도 말한다.)
        - 멀티태스킹: 여러 작업을 동시에 수행하는 것처럼 보이거나 실제로 동시에 수행하는 것
        - 비선점형: 멀티태스킹의 각 작업을 수행하는 참여자들의 실행을 운영체제가 강제로 일시 중단시키고 다른 참여자를 실행하게 만들 수 없다는 뜻.
    - 따라서 각 참여자들이 서로 자발적으로 협력해야만 비선점형 멀티태스킹이 제대로 작동할 수 있다.
- 코틀린의 코루틴 지원: 일반적인 코틀린
    - 코틀린의 코루틴 지원 기본 기능들은 `kotlin.coroutine` 패키지 밑에 있다.
    - 코틀린이 지원하는 기본 기능을 활용해 다양한 형태의 코루틴은 `kotlinx.coroutines` 패키지 밑에 있다.
        - [kotlinx.coroutines](<https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects>)

### kotlinx.coroutines

    - 코루틴을 만들어주는 코루틴 빌더(coroutine builder)
    - 코루틴 빌더에 원하는 동작을 람다로 넘겨서 코루틴을 만들어 실행하는 방식으로 코루틴을 활용

### kotlinx.coroutines.CoroutineScope.launch

```kotlin
package coroutine

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

fun now() = LocalDateTime.now().toLocalTime().truncatedTo(ChronoUnit.MILLIS)
fun log(msg: String) = println("${now()} : ${Thread.currentThread()}: ${msg}")

fun launchInGlobalScope() {
    GlobalScope.launch {
        log("coroutine started")
    }
}

fun main(args: Array<String>) {
    log("main() started")
    launchInGlobalScope()
    log("launchInGlobalScope() executed")
    Thread.sleep(5000L)
    log("main() terminated")
}

/*
* 15:54:51.569 : Thread[main,5,main]: main() started
* 15:54:51.691 : Thread[main,5,main]: launchInGlobalScope() executed
* 15:54:51.700 : Thread[DefaultDispatcher-worker-1,5,main]: coroutine started
* 15:54:56.699 : Thread[main,5,main]: main() terminated
* */

runBlocking

package coroutine

import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

fun now() = LocalDateTime.now().toLocalTime().truncatedTo(ChronoUnit.MILLIS)
fun log(msg: String) = println("${now()} : ${Thread.currentThread()}: ${msg}")

fun runBlockingExample() {
    runBlocking {
        launch {
            log("coroutine started")
        }
    }
}

fun main(args: Array<String>) {
    log("main() started")
    runBlockingExample()
    log("runBlockingExample() executed")
    log("main() terminated")
}

/*
* 16:18:28.613 : Thread[main,5,main]: main() started
* 16:18:28.708 : Thread[main,5,main]: coroutine started
* 16:18:28.710 : Thread[main,5,main]: runBlockingExample() executed
* 16:18:33.715 : Thread[main,5,main]: main() terminated
* */

yield

package coroutine

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield

fun log(msg: String) = println("${now()} : ${Thread.currentThread()}: ${msg}")

fun yieldExample() {
    runBlocking {
        launch {
            log("1")
            yield()
            log("3")
            yield()
            log("5")
        }
        log("after first launch")
        launch {
            log("2")
            delay(1000L)
            log("4")
            delay(1000L)
            log("6")
        }
        log("after second launch")
    }
}

fun main(args: Array<String>) {
    log("main() started")
    yieldExample()
    log("after runBlocking")
    log("yieldExample() executed")
    log("main() terminated")
}

/*
* 16:29:25.512 : Thread[main,5,main]: main() started
* 16:29:25.622 : Thread[main,5,main]: after first launch
* 16:29:25.629 : Thread[main,5,main]: after second launch
* 16:29:25.632 : Thread[main,5,main]: 1
* 16:29:25.633 : Thread[main,5,main]: 2
* 16:29:25.644 : Thread[main,5,main]: 3
* 16:29:25.644 : Thread[main,5,main]: 5
* 16:29:26.646 : Thread[main,5,main]: 4
* 16:29:27.651 : Thread[main,5,main]: 6
* 16:29:27.655 : Thread[main,5,main]: after runBlocking
* 16:29:27.655 : Thread[main,5,main]: yieldExample() executed
* 16:29:27.655 : Thread[main,5,main]: main() terminated
* */
  1. launch는 즉시 반환된다
  2. runBlocking은 내부 코루틴이 모두 끝난 다음에 반환된다.
  3. delay()를 사용한 코루틴은 그 시간이 지날 때 까지 다른 코루틴에게 실행을 양보한다.

kotlin.coroutines.CoroutineScope.async

fun sumAll() {
    runBlocking {
        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()}")

        log("after await all & add")
    }
}

fun main() {
    sumAll()
}

/*
* 17:09:01.850 : Thread[main,5,main]: after async(d1)
* 17:09:01.864 : Thread[main,5,main]: after async(d2)
* 17:09:01.867 : Thread[main,5,main]: after async(d3)
* 17:09:02.888 : Thread[main,5,main]: 1+2+3 = 6
* 17:09:02.889 : Thread[main,5,main]: after await all & add
* */

코루틴 컨텍스트와 디스패처

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
package coroutine

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking

fun dispatcherExample() {
    runBlocking {
        launch { // 부모 컨텍스트를 사용 이경우(main)
            println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
        }

        launch(Dispatchers.Unconfined) { // 특정 스레드에 종속되지 않음 ? 메인 스레드 사용
            println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
        }

        launch(Dispatchers.Default) { // 기본 디스패처를 사용
            println("Default : I'm working in thread ${Thread.currentThread().name}")
        }

        launch(newSingleThreadContext("MyOwnThread")) { // 새 스레드 사용
            println("newSingleThreadContext : I'm working in thread ${Thread.currentThread().name}")
        }
    }
}

fun main(args: Array<String>) {
    dispatcherExample()
}

/*
* Unconfined : I'm working in thread main
* Default : I'm working in thread DefaultDispatcher-worker-2
* main runBlocking : I'm working in thread main
* newSingleThreadContext : I'm working in thread MyOwnThread
* */

코루틴 빌더와 일시 중단 함수

suspend

package coroutine

import kotlinx.coroutines.*

suspend fun yieldThreeTimes() {
    log("1")
    delay(1000L)
    yield()
    log("2")
    delay(1000L)
    yield()
    log("3")
    delay(1000L)
    yield()
    log("4")
}

fun main(args: Array<String>) {
    runBlocking {
        launch { yieldThreeTimes() }
    }
}

제네레이터 빌더

kotlin-example