Skils/Kotlin

[Kotlin] - 코루틴

재한 2023. 3. 5. 16:16

코루틴

비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다.


비동기 vs 동기

예를 들면,

요리를 할 때

동생 같은 요리 초보는 프라이팬을 하나만 사용하면서 요리를 한다.

이러한 방식을 동기라고 한다.

나같은 요리 고수는 프라이팬을 여러 개 사용하면서, 칼질도 하는 등 여러 작업을 동시에 한다.

이러한 방식을 비동기라고 한다.


그렇다면 비동기가 왜 필요할까?

만약 내가 버튼을 누름과 동시에 데이터베이스에서 접근해서 데이터베이스를 읽어 들이는데,
그 데이터베이스를 기다리는 동안 화면 UI는 데이터베이스에서 정보가 들어오지 않았기에, 계속 기다려야 하고, 그렇게 되면 사용자 입장에서는 텅 빈 화면을 바라봐야 한다.
이러한 상황이 발생되면 사용자도 불편하고, 앱도 별로라는 평가를 많이 받기에,
우리는 프라이팬의 용도를 정해서 그 용도마다 작업을 해야 한다.
이 프라이팬을 안드로이드의 스레드라고 생각하면 된다.

코루틴은 코루틴 스코프 내에서 실행되며, 코루틴은 항상 자신이 속한 스코프를 참조해야 한다.

 


코루틴 스코프

  1. GlobalScope : 앱의 생명주기와 함께 동작하기 때문에 실행 도중에 별도 생명 주기 관리가 필요 없다.
    시작~종료까지 긴 기간 실행되는 코루틴의 경우에 적합하다.
  2. CoroutineScope : 버튼을 눌러 다운로드하거나 서버에서 이미지를 열 때 등 필요할 때만 열고 완료되면 닫아주는 코루틴 스코프를 사용할 수 있다.
    1. CoroutineScope는 GlobalScope와 달리 디스패처를 지정할 수 있는데, 이는 코루틴이 실행될 스레드를 지정해 주는 의미.
  3. ViewModelScope : jetpack 아키텍처의 뷰모델 컴포넌트 사용 시 ViewModel 인스턴스에서 사용하기 위해 제공되는 스코프이다. 해당 스코프로 실행되는 코루틴은 뷰모델 인스턴스가 소멸될 때 자동으로 취소된다.

CoroutineContext의 구성요소

  1. 코루틴이 실행 중인 취소 가능한 작업을 표현하는 job
  2. 코루틴과 스레드의 연관을 제어하는 dispatcher

코루틴 디스패처의 종류는 3가지가 있고, 각 디스패처는 코루틴을 적당한 스레드에 할당해 준다.

코틀린 코루틴의 디스패처

  1. Main : 사용자 입력이 처리되는 UI 작업.
  2. IO : 이미지 다운로드, 파일 입출력 등 입출력에 최적화되어있는 디스패쳐(네트워크, DB 등 백그라운드에서 필요한 작업을 하는데 적합)
  3. Default : CPU를 많이 쓰는 작업에 최적화(정렬이나 무거운 계산 작업 등에 적합)

코루틴의 상태 관리 메서드

Cancel

  1. 코루틴의 동작을 멈추는 상태관리 메서드로, 하나의 스코프 안에 여러 코루틴의 존재하는 경우 하위 코루틴 또한 모두 멈추게 한다.
suspend fun main(){
    val printer = GlobalScope.launch(Dispatchers.Default) {
    var i = 1
    while(isActive) {
        println(i++)
        }
    }
    delay(100) // job이 어느정도 실행될 시간을 준다.
    Printer.cancel() //실행 취소
}

여기서 만약 while(isActive)가 들어간 이유는 코루틴이 취소됐는지 검사를 해야 하기 때문이다.

suspend fun main(){
    val printer = GlobalScope.launch(Dispatchers.Default) {
    var i = 1
    while(1) { //이 부분이 달라짐.
        println(i++)
        }
    }
    delay(100) // job이 어느정도 실행될 시간을 준다.
    Printer.cancel() //실행 취소
}

위 코드와 같은 코드지만 해당 코드는 실행해 보면, 계속 실행되는 것을 알 수 있다.

왜냐하면 cancel을 했지만, 취소됐는지에 대한 검사가 이루어지지 않아서, 계속 printer가 실행되는 것이다.

 

Join

  1. 코루틴 내부에서 여러 lanch 블록이 있는 경우 순서를 정해주는 용도.
  2. join을 통해서 작업을 순차적으로 실행할 수 있다.
CoroutineScope(Dispatchers.Default).launch{
    launch {
        for( i in 0 ..5){
            delay(500)
               prinln(i)
        }
      }
   }.join()

    launch{
        for( in 6 .. 10){
            delay(500)
            println(i)
         }
       }
 }

이렇게 잡에 join을 부여해 주면 작업을 순차적으로 진행할 수 있다.

 

Suspend

  1. 코루틴 안에서 사용되면 suspend 함수가 호출될 경우 이전까지의 코드의 실행이 멈추며 suspend 함수가 처리가 완료된 후 멈춰있던 원래 스코프의 다음 코드가 실행된다.
  2. Suspend 함수가 호출되는 순간 이전까지의 실행은 멈추고, Suspend의 작업이 모두 완료된 이후의 멈췄던 실행이 다시 시작된다.
  3. 코드가 잠시 멈추지만, 스레드의 중단은 없다.

코루틴 빌더

launch()

  1. 코루틴을 시작하고, 실행 중인 작업의 상태를 추적하고 변경할 수 있는 job 객체를 반환함.
  2. 결과를 반환하지 않기에, 동시성 작업이 결과를 만들어내지 않는 경우 적합하다.

async()

  1. 결과나 예외를 반환함.
  2. 실행 결과는 Deferred의 인스턴스를 통해서 반환됨.
  3. await() 메서드는 계산이 완료되거나, 계산작업이 취소될 때까지 코루틴을 중지시킴.

Job

  1. 동시성 작업의 생명 주기를 표현하는 객체.
  2. 잡을 사용 하면 작업 상태를 추적하고 필요할 때 작업을 취소할 수 있다.

Job의 상태

  1. isActive()
  2. isCompleted()
  3. isCancelled()

일단은 코루틴에 대해서 아주 겉핥기식으로 공부했습니다.

너무 어렵네요..

차차 공부하고 살을 붙여서 글을 마무리할 예정입니다.. ㅠㅠㅠㅠㅠ


느낀 점

  • 비동기라는 개념을 정확하게는 알지 못했는데, 코루틴을 공부하면서 알게 되었고, 비동기 방법이 효율적인 스레드의 사용이 가능해진다는 점을 알았다.
  • 네트워크 작업, DB를 읽는 등 시간이 오래 걸리는 작업을 무작정 기다리지 않고, 코루틴을 사용해서 효과적으로 작업을 처리할 수 있다.
    • 이때까지 계속 데이터를 받아 오기 전에 화면이 먼저 노출되어서, 불편하게 코드를 짰었는데, 코루틴을 사용하면 될 것 같다.
  • 정리한 코루틴은 진짜 코루틴의 손톱..? 코루틴은 다 좋지만, 코드 복잡도가 엄청 올라가서, 사용자의 실력이 중요한 것 같다.
  • 비동기를 처리하는 방법 중 rxjava도 있다고 하는데, 뭐가 더 좋다기보다는 안드로이드 쪽에서 코루틴을 밀어주는 것 같다..? ㅎ