[kotlin] Coroutine 기본 - 예제를 통한 코루틴 동작 순서

조갱·2023년 4월 9일
1

Coroutine

목록 보기
6/9

코루틴에 대해 깊게 이해하기 전에, 간단하게 동작 방식을 먼저 경험해보자.

적어도 코루틴의 동작 순서를 먼저 경험해보고, 코루틴에 대해 깊게 공부한다면
'아 이래서 동작 순서가 이랬구나~' 하고 더 공감하며 이해할 수 있을것 같다.

* details 태그를 통해 정답을 접었다 폈다 보여주고 싶었는데 velog에서는 details 태그를 지원하지 않는다고 한다 ㅠㅠ

시작하기 전에

모든 question 함수들은 main함수의 runBlocking 안에서 수행된다.
아래 예제에 대해 출력되는 문자열을 맞히면 된다.

fun main(){
    runBlocking { question() }
}

fun logging(msg: String) {
    println("${Thread.currentThread().name} - ${LocalDateTime.now()} : $msg")
}

Level0

코루틴인듯 코루틴 아닌듯

문제 1

제일 기본적인 로직입니다.

fun question1(){
    logging("1")
    runBlocking {
        logging("2")
    }
    logging("3")
}

정답 : 1 2 3

문제 2

runBlocking이 다른 함수에서 호출됩니다.

fun question2(){
    logging("1")
    question2_2()
    logging("3")
}

fun question2_2(){
    runBlocking {
        logging("2")
    }
}

정답 : 1 2 3

Level1

기본적인 코루틴에 대해 예측해봅니다.

문제 3

launch를 예측해봅시다.

suspend fun question3(){
    logging("1")
    CoroutineScope(Dispatchers.IO).launch {
        logging("2")
        delay(2000)
        logging("3")
    }
    logging("4")
    delay(2500)
}

정답 : 1 4 2 3

1 2 4 3 이 될 수도 있는데, 일반적으로는 launch를 실행하는 비용 (서브루틴) 으로 인해 4가 먼저 출력됩니다.

문제 4

withContext를 예측해봅시다.

suspend fun question4(){
    logging("1")
    withContext(Dispatchers.IO){
        logging("2")
        delay(1000)
        logging("3")
    }
    logging("4")
    delay(100)
}

정답 : 1 2 3 4
withContext는 부모 context에 대해 순서를 보장합니다.

문제5

async를 예측해봅시다.

suspend fun question5(){
    logging("1")
    GlobalScope.async {
        logging("2")
        delay(1000)
        logging("3")
    }
    logging("4")

    delay(1500)
}

정답 : 1 4 2 3

Level2

코루틴에 옵션을 넣어봅니다.

문제6

async에 join을 적용 예측해봅시다.

suspend fun question6(){
    logging("1")
    GlobalScope.async {
        logging("2")
        delay(1000)
        logging("3")
    }.join()
    logging("4")

    delay(1500)
}

정답 : 1 2 3 4

join() / await()를 사용하면 해당 코루틴이 종료될때 까지 대기합니다.
join()은 Job 객체의 메소드로, 반환형이 없이 대기만 하며,
await()는 Deffered 객체의 메소드로, 코루틴의 결과값을 반환합니다.
Deffered 인터페이스는 Job인터페이스를 상속받기 때문에, async에서 join()과 await() 둘 다 사용 가능합니다.

문제7

coroutine을 LAZY 하게 시작해봅시다.

suspend fun question7(){
    logging("1")
    val job = GlobalScope.launch(start = CoroutineStart.LAZY){
        logging("2")
    }
    delay(200)
    logging("3")
    job.start()
}

정답 : 1 3 2
launch에 start = LAZY 파라미터를 전달했기 때문에,
job 코루틴은 선언 당시 실행되는 것이 아니라 job.start() 지점에서 실행됩니다.

start가 기본값 (CoroutineStart.Default)이면 1 2 3이 출력됩니다.

문제8

coroutine을 LAZY 하게 시작해봅시다(2).

suspend fun question8(){
    logging("1")
    val job = GlobalScope.launch(start = CoroutineStart.LAZY){
        delay(200)
        logging("2")
    }
    delay(500)
    logging("3")
    job.start()
}

정답 : 1 3
start()는 단순하게 실행하는 메소드이기 때문에 job을 시작은 시켰지만
job 내부에 delay(200)을 기다리다가 어플리케이션이 종료됩니다.
따라서 2는 출력되지 않습니다.

2를 출력시키고 싶다면, job.join()을 통해 coroutine이 끝날 때까지 기다릴 수 있습니다.

Level3

간단한 코루틴을 섞어서 사용해봅니다.

문제 9

Runblocking + launch

suspend fun question9(){
    logging("1")
    runBlocking {
        logging("2")
        launch {
            logging("3")
            delay(1000)
            logging("4")
        }
        logging("5")
    }
    logging("6")
}

정답 : 1 2 5 3 4 6

문제 10

이번엔 launch가 아닌 GlobalScope.launch 입니다.

suspend fun question10(){
    logging("1")
    runBlocking {
        logging("2")
        GlobalScope.launch {
            logging("3")
            delay(1000)
            logging("4")
        }
        logging("5")
    }
    logging("6")
}

정답 : 1 2 5 3 6 (4은 출력되지 않습니다.)

문제 11

runBlocking과 GlobalScope.launch의 순서를 바꿔봅니다.

suspend fun question11(){
    logging("1")
    GlobalScope.launch {
        logging("2")
        runBlocking {
            logging("3")
            delay(1000)
            logging("4")
        }
        logging("5")
    }
    logging("6")
    delay(2000)
}

정답 : 1 6 2 3 4 5

Level4

코루틴에 옵션을 추가하고, 복잡하게 섞어서 사용해볼게요.

문제12

suspend fun question12() {
    logging("1")
    runBlocking {
        logging("2")
        val lazyJob = launch(start = CoroutineStart.LAZY) {
            logging("3")
            delay(200)
            logging("4")
        }
        val normalJob = launch {
            logging("5")
            lazyJob.start()
            delay(400)
            logging("6")
        }
        logging("7")
    }
    logging("8")
    delay(1000)
}

정답 : 1 2 7 5 3 4 6 8

문제13

suspend fun question13() {
    logging("1")
    runBlocking {
        logging("2")
        val lazyJob = launch(start = CoroutineStart.LAZY) {
            logging("3")
            delay(400)
            logging("4")
        }
        coroutineScope {
            logging("5")
            lazyJob.start()
            delay(200)
            logging("6")
        }
        logging("7")
    }
    logging("8")
    delay(1000)
}

정답 : 1 2 5 3 6 7 4 8

문제14

suspend fun question14() {
    logging("1")
    runBlocking {
        logging("2")
        val lazyJob = launch {
            logging("3")
            delay(200)
            logging("4")
        }
        withContext(Dispatchers.IO) {
            logging("5")
            delay(400)
            logging("6")
        }
        logging("7")
    }
    logging("8")
    delay(1000)
}

정답 : 1 2 3 5 4 6 7 8

문제15

suspend fun question15() {
    logging("1")
    runBlocking {
        logging("2")
        val lazyJob = GlobalScope.launch(start = CoroutineStart.LAZY) {
            logging("3")
            coroutineScope {
                delay(600)
                logging("4")
            }
            delay(200)
            logging("5")
        }
        coroutineScope {
            logging("6")
            lazyJob.start()
            delay(400)
            logging("7")
        }
        logging("8")
    }
    logging("9")
    delay(1000)
}

정답 : 1 2 6 3 7 8 9 4 5

Level5

마지막 관문이예요. 억지스럽게 섞어볼게요.

마지막 문제1

fun finalQuestion1() {
    runBlocking {
        logging("1")

        launch {
            logging("2")
            delay(1000L)
            logging("3")
        }

        logging("4")
        withContext(Dispatchers.IO) {
            logging("5")
            Thread.sleep(2000L)
            logging("6")
        }

        logging("7")

        runBlocking {
            logging("8")
            Thread.sleep(2000L)
            logging("9")
        }

        withContext(Dispatchers.IO) {
            logging("10")
            delay(1000L)
            logging("11")
            withContext(Dispatchers.IO) {
                logging("12")
                delay(1000L)
                logging("13")
            }
            logging("14")
        }
        logging("15")
    }

    GlobalScope.launch {
        logging("16")
        delay(1000L)
        logging("17")
    }

    runBlocking {
        logging("18")
    }

정답 : 1 4 2 5 3 6 7 8 9 10 11 12 13 14 15 16 18

17은 출력되지 않습니다.

마지막 문제2

suspend fun finalQuestion2(){
    logging("1")
    val normalJob = GlobalScope.launch {
        logging("2")
        delay(200)
        logging("3")
    }
    runBlocking {
        logging("4")
        val lazyJob  = GlobalScope.launch(start = CoroutineStart.LAZY) {
            logging("5")
            delay(600)
            logging("6")
        }
        withContext(Dispatchers.IO) {
            logging("7")
            lazyJob.start()
            delay(400)
            logging("8")
        }
        val innerNormalJob = GlobalScope.launch {
            logging("9")
            delay(800)
            logging("10")
        }
        logging("11")
    }
    logging("12")
    delay(2000)
}

정답 : 1 4 2 7 5 3 8 11 9 12 6 10

끝내며

이 글을 본 사람들은 과연 몇 문제를 맞혔는가??
나도 문제를 작성하고 정답을 맞혀보면서 몇몇 틀린 문제들이 있다.
(특히 Level5 - 마지막 문제2 바로 맞혔으면 인정,,,)
이렇게 코루틴을 다시 한번 복습해본다.

문제는 제가 열심히 고민하며 맹글었슴다,, 혹시 다른곳에서 재사용 한다면 출처 부탁드립니다!

profile
A fast learner.

0개의 댓글