Coroutine의 기능

이영훈·2022년 4월 14일
0

코루틴

코틀린 코루틴(coroutine) 개념 익히기

[Android] 코틀린(Kotlin) 코루틴(Coroutine) 한 번에 끝내기

코루틴 vs 스레드

둘 다 동시성 프로그래밍을 위한 수단이다.

Thread

스레드는 각각 Stack 메모리 영역을 할당 받는다.
하나의 스레드에 하나의 task, 작업 하나하나의 단위가 스레드이다.
Thread1에서 Task1을 실행중인데, Task2의 실행 결과 데이터가 필요하다면,
Thread1을 block 시키고 Thread2의 Task2를 실행시킨다. (Context Switching)

Coroutine

하나의 스레드에 여러개의 코루틴, 작업 하나하나의 단위가 코루틴 object이다.
코루틴 object들은 JVM heap 메모리 영역에 적재된다.
동시성 보장 수단이 Context Switching이 아닌 Programmer Switching(프로그래머가 직접 코드로 구현.)
Task1 → Task2가 Context Switching 없이 같은 스레드에서 가능하다
-> 때문에 경량화된 스레드라고 한다.
각각 다른 스레드에서 각각 코루틴을 실행시킬 수 있지만 그러면 코루틴을 사용하는 의미가 없으니 최대한 자제하자.

Scope

  1. GlobalScope
    → 앱의 생명주기와 함께 동작, 시작 ~ 종료까지 실행되는 코루틴의 경우 적합.
  2. CoroutineScope
    → 버튼을 눌러 다운로드, 서버에서 이미지 열 때 등 필요할 때만 열고 완료되면 닫아주는 코루틴의 경우 적합.
  3. ViewModelScope
    → AAC ViewModel 사용시 ViewModel에서 사용하기 위해 제공됨. 이 스코프로 실행되는 코루틴은 뷰모델 인스턴스가 소멸될 때 자동으로 취소됨.

Dispatcher

  1. Default
    → 안드로이드 기본 스레드풀 사용, CPU를 많이 쓰는 작업에 최적화. (데이터 정렬, 복잡한 연산 등)
  2. IO
    → 이미지 다운로드, 파일 IO 등 IO에 최적화됨. (네트워크, DB 등의 작업에 적합)
  3. Main
    → 안드로이드 기본 스레드에서 실행. UI와 상호작용에 최적화

상태 관리

  1. launch
    → 상태 관리(그냥 실행이라고 생각하자)

  2. async
    → 상태 관리 + 결과 반환

  3. cancel
    → 코루틴의 동작을 멈춤. 하나의 스코프 안에 여러 코루틴이 존재하는 경우 하위 코루틴 또한 모두 멈춤

    ```kotlin
    val job = CoroutineScope(Dispatchers.Default).launch {
    	val job1 = launch{
    		어떤 동작
    	}
    }
    // 이러면 job1도 캔슬됨.
    job.cancel
    ```
  4. join
    → 코루틴 내부에 여러 launch 블록이 있는 경우 모두 새로운 코루틴으로 분기 되어 동시 실행되기 때문에 순서를 정할 수 없다. 순서를 정해야 한다면 join()을 사용해서 순차적으로 실행되게 할 수 있다.

    ```kotlin
    CoroutineScope(Dispatchers.IO).launch {
    	launch {
    		어떤 동작1
    	}.join()
    	launch {
    		어떤 동작2
    	}
    }
    ```
  5. async
    → 코루틴 스코프의 결과를 받아 사용할 수 있다. 연산이 오래 걸리는 2개의 네트워크 작업이 모두 완료되고 나서 이를 처리하려면 await()를 사용할 수 있다.

    ```kotlin
    CoroutineScope(Dispatchers.IO).launch {
    	val deferred1 = async {
    		delay(500)
    		350
    	}
    	val deferred2 = async {
    		delay(1000)
    		200
    	}
    	deferred1.await() + deferred2.await()
    }
    ```
  6. suspend
    → 코루틴 내에서 사용되면 suspend 함수 호출 이전까지의 코드 실행이 멈추며, suspend 함수 처리 완료 후 멈춰있던 원래 스코프의 다음 코드가 실행됨.

    ```kotlin
    suspend fun subRoutine() {
    	어떤 동작
    }
    
    CoroutineScope(Dispathcers.IO).launch {
    	// 호출 전 동작
    	subRoutine()
    	// 여기부터의 코드는 suspend 함수가 끝나기 전까지 실행되지 않음.
    	// suspend 함수가 끝나면 다시 실행됨.
    }
    ```
    
    <aside>
    💡 코루틴이 실행되다가 일시 정지하는 경우 코틀린 런타임은 해당 코루틴이 실행되던 스레드에 다른 코루틴을 할당하여 실행되게 한다.
    그리고 다시 이전 코루틴이 재개될 때 사용 가능한 스레드를 코틀린 런타임이 할당해준다. 효울적인 스레드 활용이 가능하다. suspend의 경우도 마찬가지.
    
    </aside>
  7. withContext
    → suspend 함수를 코루틴 스코프에서 사용할 때 호출한 스코프와 다른 디스패쳐를 사용해야할 때가 있는데, 이 때 withContext를 사용한다. (나는 지금까지 스코프 내에서 스코프를 별도로 다시 생성했는데 바보같은 짓이었다.)

    ```kotlin
    CoroutineScope(Dispatchers.IO).launch {
    	val result = withContext(Dispatchers.Main) {
    		UI 관련 동작
    	}
    }
    ```
  8. runBlocking
    → 코루틴 시작 ~ 완료 까지 현재 스레드를 중단 시킨다. 코루틴의 취지와는 정반대.
    그러나 코드 테스트, 레거시 코드 및 라이브러리 통합 시 유용하다고 한다.
    잘 모르겠으니 쓰지 말자.

  9. Job
    → launch, async 등 빌더를 호출하면 job 인스턴스가 반환됨. (launch = job, async = deffered인데 deffered가 job을 상속 받음) 코루틴 생명주기를 job으로 관리할 수 있고, 내부에서 다시 빌더를 호출하면 자식 job으로 생성됨. 부모 job 취소 시 자식 job도 취소되며 반대는 X.
    그러나 launch로 생성한 자식 job에서 예외 발생 시 부모 job을 취소시킴
    async는 취소되지 않는데 이는 반환 결과에 예외도 포함시켜버리기 때문.

profile
And dev

0개의 댓글