ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코루틴(Coroutine) - 기본개념
    Android(+ Kotlin) 2020. 1. 16. 16:17

    [2.18 수정] 코루틴은 Kotlin언어를 개발한 jetbrains에서 만들어졌다.(해당 원론 개념은을 뜻한바는 아니였습니다.)

    ko.wikipedia.org/wiki/%EC%BD%94%EB%A3%A8%ED%8B%B4 

    "Kotlin 1.3에서 추가되었으며 다른 언어에서 확립된 개념을 기반으로 합니다"(developer.android.com/kotlin/coroutines?hl=ko)

    Kotlin언어에서 사용할 수 있는 coroutine 비동기 솔루션입니다.(Java에서는 사용할 수 없다.)

     

    서브루틴(subroutine)

    먼저 서브루틴 개념이 필요하다, 

    하나의 함수를 예를 들어 파라미터를 받고 시작해서 끝 지점에서 종료되는 방식이 서브루틴이다.

     

    코루틴(Cooutine)

    서브루틴가 유사하게 단일지점에서 시작해서 끝 지점에서 종료가 된다.

    하지만 중간에 함수 실행을 중단되었다 중단 지점 지점에서 재 진행한다.(임의 지점에서 멈춤, 해당 지점에서 재개)

    결국 비동기형 프로그래밍으로 보아도 무방할 것 같다.

     

    - 그럼 코루틴의 장점은 무엇일까?

    - 비동기 코드를 작성할 때 asynctask와 같이 별도의 클래스 또는 스레드를 생성할 필요 없이 순서대로 코드를 작성할 수 있다. 

    - light-weight thread이다. 기존 비동기 코드(asynctask, thread) 보다 소모비용이 작다,

    - google에서 AsynckTask가 deprecate 되며 코루틴을 추천하고 있고 있다.

    - 현재 코틀린 1.3 버전부터 공식적으로 추가되었으며 mvvm등에 구조에 맞는 scope 제공한다.(대세는 corutine!!)

    rxjava 보다 러닝 커브는 낮다고 생각이 든다. 하지만 rx가 많은 기능을 갖고 있기 때문에 앱 기능에 대해 더 체크해보고 비동기 라이브러리를 선택해야 한다. (서버와의 api통신 정도의 어플은 coroutine정도도 충분할 것이다.)

     

    https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html

     

    Coroutines Guide - Kotlin Programming Language

     

    kotlinlang.org

    공식 문서 기반으로 정리해 보았다.

     

    기본 사용방법

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

    sleep을 한 이유는? - 코루틴은 비동기이라 실행 중 메인 프로세스가 종료되면 해당 작업이 함께 종료된다.

     

    runBlocking

    해당 runBloking은 기본적으로 CoroutineScope를 내장하고 있다. (GlobalScope.launch 도 해당 스코프를 내장)

    runBlocking {
        delay(2000L)
    }

    메인 스레드 진행을 막고 해당 범위가 종료된 후 다음 프로세스를 진행한다. 위 sleep(2000)을 위 코드로 대처할 수 있다.

     

    Job

    코루틴의 동작을 컨트롤할 수 있다. launch를 통해 Job 타입으로 반환 값이 발생된다.

    * Job에서 사용할 수 있는 메서드

    1. start -  동작 상태 반환(동작중 - true)
    2. join - 동작이 끝날 때까지 대기한다. (메모리에서 종료가 될 때까지 대기한다.)
    3. cancel - 종료 요청한다.
    4. cancelAndJoin - 종료 요청과 함께 메모리에서 종료가 될때까지 대기한다.
    5. cnacelChileren - 부모를 제외한 하뤼 루틴들을 종료한다.

     

     

    Job관련 상세 설명 사이트 - https://thdev.tech/kotlin/2019/04/08/Init-Coroutines-Job/

    더 자세한 내용은 위 링크를 참조하자!

    val job = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    
    println("Hello, ")
    job.join()

    위 sleep, runBlocking을 join으로 대처

     

    리팩토링 함수 추출

    launch 내부를 보다 깔끔한 코드로 리팩토링 할 때 아래와 같이 함수를 만들어서 사용이 가능하다.

    suspend라는 키워드를 사용하여 언제든 지연 및 재개될 수 있는 함수로 정의할 수 있다.(IDE에서 빨간 밑줄이 표시되며 자동완성을 통해 조정된다.)

    fun main() = runBlocking {
        launch { doWorld() }
        println("Hello,")
    }
    
    suspend fun doWorld() {
        delay(1000L)
        println("World!")
    }

    doWorld() 함수 내부의 delay동안 코루틴이 일시 중단된다. 그동안 다른 작업이 진행되며 다시 print문이 실행된다.

     

    취소(Cancel)

    fun main() = runBlocking {
        var job = launch {
            repeat(1000) {i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        }
        delay(1300L)
        println("main: I'm tired of waiting!")
        job.cancel()
        job.join() // 위 처럼 종료, 대기로 작업
        job.cancelAndJoin() // 간단하게 한줄로 작업 가능하다.
        println("main: Now I can quit.")
    }

    진행 중 취소가 가능하며 1~1000까지 반복되나 중간에서 cancel을 통해 종료 요청이 들어가 종료가 된다.

    join or cancelAndJoin 사용하여 보다 안정적으로 프로세스를 종료하자!

     

    delay(1300L)
    println("1300 waiting end time : ${Date().time - nowTime}")
    println("main: I'm tired of waiting!")
    job.cancel()
    job.join() // job.cancelAndJoin()
    println("call by cancel time : ${Date().time - nowTime}")
    println("main: Now I can quit.")

    0.004초 만에 종료가 되었다.(성능에 따라 상이)

    join or cancelAndJoin 메서드를 사용하면 0.010초 만에 종료되었다. 0.006초 가량 차이가 났는데, 이 이유는 join에서 메모리 해제까지의 작업에 소요되는 시간이 포함되어 있을 것이라고 추측할 수 있다.

     

    타임아웃(TimeOut)

    코루틴이 실행이 중일 때 join을 통해 프로세스를 마냥 기다릴 수 없다 android에서는 ANR 가능성을 배제할 수 없다. 이럴 때를 대비한 기능인 듯하다.

    fun main() = runBlocking {
        withTimeout(1300L) {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        }
    }

    위 코드와 같이 실행하면 TimeoutCancellationException 이 나타난다. 1.3초 동안 진행 후 타임아웃을 걸었기 때문이다.

    exception을 throws 혹은 try/catch를 통해 예외처리를 진행하여도 되지만 좋은 방식이 아니다.(개인적 생각)

     

    withTimeoutOrNull을 활용하면 해당 시간이 초과되었을 때 null을 return 한다. 아래 방식이 더 안정적이라 생각이 든다.

    fun main() = runBlocking {
        val result = withTimeoutOrNull(1300L) {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
            println("Done") // 해당 프로세스가 제시간에 완료되었을때 나타남.
        }
        println("Result is $result")
    }

     

    끝.

    댓글

Designed by Tistory.