[kotlin] 코루틴이란?, runBlocking?, suspendCancellableCoroutine

2021. 6. 3. 14:39모바일/Android_Kotlin

코루틴?

  • 비동기 라이브러리
  • 경량 스레드
  • OS에 의존적인 스레드와는 달리 스레드간 컨텍스트 전환 비용이 발생하지 않으며, 개발자가 직접 중지 지점을 선택할 수 있다.

기본적인 사용법

val scope = CoroutineScope(Dispatchers.Default)
scope.launch{
    //do
}

scope.launch(Disaptchers.IO){
    //some I/O
}
  • 위 코드처럼 어느 스케줄러에서 비동기 로직이 실행될지 결정할 수 있다.
  • 이런식으로 디스패치를 사용해서 어느 스레드풀에서 어느 로직이 실행될지를 결정할 수 있고 로직들의 규모를 세세하게 나눌 수 있다.

서스펜딩(suspending)

  • 코루틴이 실행 중인 스레드를 블록킹하지 않으면서 실행 중인 코루틴을 잠시 중단시킬 수 있는 중단 지점 함수
  • 서스펜딩 함수를 호출하는 시점에 현재 실행중인 코루틴은 잠시 중단되고, 그 때 남게 되는 스레드는 다른 코루틴에 할당될 수 있음을 의미한다
  • 서스펜딩 함수의 로직이 끝났을 때에 중단되었던 코루틴은 다시 실행 준비가 됩니다.

기본적인 사용법

suspend fun asyncFunction(){

}

네트워크 호출 사용법

suspend fun netWorkCall(): String{
    delay(1000)
    return "data from network"
}
suspend fun uiFunction(){
    val data = netWorkCall()
    println(data)
}

fun main(){
//    println(alphabetWith())
//    println(alphabetApply())
    //callFunction()
    val scope = CoroutineScope(Dispatchers.Default)

    scope.launch {
        println("coroutine. ${Thread.currentThread().name}")
        uiFunction()
    }

    Thread.sleep(500)

    scope.launch {
        println("another coroutine. ${Thread.currentThread().name}")
    }

    println("main is done. ${Thread.currentThread().name}")

    Thread.sleep(2000)
}

사용법2

fun main(){
    start()
    다른
    함수
    들
}

fun start(){
    startCoroutine{
        함수1()
        함수2()
        함수3()
    }       
}
suspend 함수1(){
    delay(1000)
}
suspend 함수2(){
    delay(1000)
}
suspend 함수3(){

}

설명

  • main 함수를 실행하면 coroutine이 시작된다.
  • 함수1() 에서 suspend함수를 만나고 start함수를 탈출한다
  • 메인쓰레드가 start함수를 탈출하고 다른 코드들 (다른함수들)을 실행한다.
  • 함수1()은 다른 쓰레드에서 계속 실행중이다.
  • 메인쓰레드가 다른 코드들을 실행하다가 함수1()이 끝나면 탈출했던 코루틴으로 돌아온다
  • 함수1() 아래 함수인 함수2()부터 resume된다.

withContext와 coroutineScope의 비교

두 함수 모두 suspend fun으로서 코루틴 내부에서 블록을 중단시키기 때문에 유사해 보인다.

그러나, coroutineScope는 withContext의 한 유형으로 볼 수 있다.

즉, coroutineScope는 withContext(this.coroutineContext) 와 본질적으로 같은 의미를 지닌다.

coroutineScope는 Dispatcher를 설정할 수 없다 (무조건 현재 호출한 context를 사용하기 때문이다)

- coroutineScope는 에러처리 등의 목적으로 특정 코드를 하나의 블럭으로 묶고 싶을 때 사용

- withContext는 해당 코드블럭을 특정 Context에서 실행하고 싶을 때 사용하는 용도 (네트워크 작업을 위한 IO Dispatcher 등)

하지만 coroutineScope 내에서 Exception을 Throw하면 전체 부모 루틴이 중지되는데 아래의 문구를 살펴보면 에러 핸들링 목적으로 사용한다는 말이 잘 이해가 되지 않는다.

사용법 3 (with runBlocking)

fun main() {

    println("run")
    CoroutineScope(Dispatchers.Default).launch {
        (0..10).forEach{
            val sum = (it..10).toMutableList().sum()
            println(sum)
        }
    }
    println("wait")
    runBlocking {
        delay(2000L)
    }
    println("Test end")

}

//출력
run
wait
55
55
54
52
49
45
40
34
27
19
10
//여기서 main함수가 블록킹 되면서 2초 후에 아래 문자 출력
Test end

언제 사용하는가?

  • 명확한 IO를 보장하고, 데이터으 ㅣ동기화가 필요한 경우와, UnitTest작성 시에 사용하는게 좋을것이다

runBlocking과 사용

fun main() = runBlocking {
    println("run")
    CoroutineScope(Dispatchers.Default).launch {
        println("왜안됨")
    }
    println("The End")
}


//출력
run
The End
왜안됨
  • 위 코드에서 runBlocking{} 빌더로 생성된 코루틴 블록은 lanch빌더를 이용해 생성한 코루틴이 종료될때까지 대기한 후 종료된다.
fun main()  {
    println("run")
    CoroutineScope(Dispatchers.Default).launch {
        println("왜안됨")
    }
    println("The End")
}
//출력
run
The End
  • 위 코드처럼 실행되는 코루틴은 호출(실행) 쓰레드를 블록하지 않기 때문에 메인함수가 종료되면서 메인 함수를 실행한 메인 스레드 역시 종료되어 프로그램이 끝난다.
fun main()  {
    println("run")
    CoroutineScope(Dispatchers.Default).launch {
        println("왜안됨")
    }
    println("The End")
    runBlocking {
        delay(2000L)
    }
}

runBlocking 없이 사용

suspend fun main()  {
    println("run")
    hello()
    println("The End")

}
suspend fun hello(){
    suspendCancellableCoroutine <hi?>{hi->
        println("왜안됨")
        val hello2 = hi()
        hi.resume(hello2)
    }
}
//출력
run
왜안됨
The End