Android 공부/Coroutine

[XX캠퍼스] 05. Kotlin Coroutines & Flow ( 컨텍스트와 디스패처 )

Machine_웅 2022. 7. 25. 16:26
728x90
반응형

디스페처 - 어떤 쓰레드에서 수행될지 결정

코루틴 디스패처

코루틴의 여러 디스패처 Default, IO, Unconfined, newSingleThreadContext을 사용해봅시다.

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    launch {
        println("부모의 콘텍스트 / ${Thread.currentThread().name}")
    }

    launch(Dispatchers.Default) {
        println("Default / ${Thread.currentThread().name}")
    }

    launch(Dispatchers.IO) {
        println("IO / ${Thread.currentThread().name}")
    }

    launch(Dispatchers.Unconfined) {
        println("Unconfined / ${Thread.currentThread().name}")
    }

    launch(newSingleThreadContext("Fast Campus")) {
        println("newSingleThreadContext / ${Thread.currentThread().name}")
    }
}

수행결과 
Default / DefaultDispatcher-worker-1 @coroutine#3
Unconfined / main @coroutine#5
IO / DefaultDispatcher-worker-2 @coroutine#4
부모의 콘텍스트 / main @coroutine#2
newSingleThreadContext / Fast Campus @coroutine#6

디스페처의 종류 

  1. Default코어 수에 비례하는 스레드 풀에서 수행합니다. ( 복잡한 연산 ) 
  2. IO는 코어 수 보다 훨씬 많은 스레드를 가지는 스레드 풀입니다. IO 작업은 CPU를 덜 소모하기 때문입니다.                 ( 파일읽어오기 또는 네트워크 통신 )
  3. Unconfined는 어디에도 속하지 않습니다. 지금 시점에는 부모의 스레드에서 수행될 것입니다.                                 (앞으로 어디에서 수행될지 모르는 특징을 가지고 있다. )
  4. newSingleThreadContext는 항상 새로운 스레드를 만듭니다. (이름을 지정해줄수 있다. )

* Default, IO 는  스레드 풀에 있는 스레드를 사용하기 때문에 

 다른 스레드가 이미 많이 쓰이고 있는경우 할당받지 못할 수 도 있다. 

( 항상 할당받아야 하는경우에는 newSingleThreadContext 를 쓸 수 있다. ) 

 


async에서 코루틴 디스패처 사용

launch외에 async, withContext 등의 코루틴 빌더에도 디스패처를 사용할 수 있습니다.

 

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    async {
        println("부모의 콘텍스트 / ${Thread.currentThread().name}")
    }

    async(Dispatchers.Default) {
        println("Default / ${Thread.currentThread().name}")
    }

    async(Dispatchers.IO) {
        println("IO / ${Thread.currentThread().name}")
    }

    async(Dispatchers.Unconfined) {
        println("Unconfined / ${Thread.currentThread().name}")
    }

    async(newSingleThreadContext("Fast Campus")) {
        println("newSingleThreadContext / ${Thread.currentThread().name}")
    }
}

사용예시 
Unconfined / main @coroutine#5
Default / DefaultDispatcher-worker-1 @coroutine#3
IO / DefaultDispatcher-worker-3 @coroutine#4
부모의 콘텍스트 / main @coroutine#2
newSingleThreadContext / Fast Campus @coroutine#6

Confined 디스패처 테스트

Confined는 처음에는 부모의 스레드에서 수행됩니다.

하지만 한번 중단점(suspension point)에 오면 바뀌게 됩니다.

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    async(Dispatchers.Unconfined) {
        println("Unconfined / ${Thread.currentThread().name}")
        delay(1000L)
        println("Unconfined / ${Thread.currentThread().name}")
    }
}

Confined는 중단점 이후 어느 디스패처에서 수행될지 예측하기 어렵습니다.

가능하면 확실한 디스패처를 사용합시다.

( 사용하지 않는 것을 추천 한다. )

 

 


부모가 있는 Job과 없는 Job

코루틴 스코프, 코루틴 컨텍스트구조화되어 있고 부모에게 계층적으로 되어 있습니다.

코루틴 컨텍스트의 Job 역시 부모에게 의존적입니다. 부모를 캔슬했을 때의 영향을 확인해보세요.

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> { // 증부모
    val job = launch {  // 부모
        launch(Job()) { // Job을 만들어버리면 부모가 누군지 모른다. 
            println(coroutineContext[Job])
            println("launch1: ${Thread.currentThread().name}")
            delay(1000L)
            println("3!")
        }

        launch {
            println(coroutineContext[Job])
            println("launch2: ${Thread.currentThread().name}")
            delay(1000L)
            println("1!")
        }
    }

    delay(500L)
    job.cancelAndJoin()
    delay(1000L)
}

실행결과
"coroutine#3":StandaloneCoroutine{Active}@b684286
launch1: main @coroutine#3
"coroutine#4":StandaloneCoroutine{Active}@36d64342
launch2: main @coroutine#4
3!   // 위에서 Job을 만들어 버려서 부모가 누군지 모른다.

job.cancelAndJoin() 실행 후의 delay가 없다면 어떻게 될까요?

=>  delay(1000L) 을 없애면, 종료된다. 

val job을 기다리고 있었는데  Job()이  자식이 아니기때문에 시스템은 기다려주지 않고 종료 .

 

 


부모의 마음

구조화되어 계층화된 코루틴은 자식들의 실행을 지켜볼까요?

 

import kotlin.system.*
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    val elapsed = measureTimeMillis {
        val job = launch { // 부모
            launch { // 자식 1
                println("launch1: ${Thread.currentThread().name}")
                delay(5000L)
            }

            launch { // 자식 2
                println("launch2: ${Thread.currentThread().name}")
                delay(10L)
            }
        }
        job.join()
    }
    println(elapsed)
}

실행결과 
launch1: main @coroutine#3
launch2: main @coroutine#4
5024

부모를 join해서 기다려 보면 부모는 두 자식이 모두 끝날 때까지 기다린다는 것을 알 수 있습니다.

 

 


코루틴 엘리먼트 결합

여러 코루틴 엘리먼트를 한번에 사용할 수 있다. + 연산으로 엘리먼트를 합치면 된다.

합쳐진 엘리먼트들은 coroutineContext[XXX]로 조회할 수 있다.

 

디스페처, 네임  등을 붙일 수 있다. 

import kotlin.system.*
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    launch {
    	// 엘리먼트1 Dispatchers.IO  엘리먼트 2  CoroutineName("launch1")
        launch(Dispatchers.IO + CoroutineName("launch1")) { // 얘네를 Coroutine Context 라고 불른다. 
            println("launch1: ${Thread.currentThread().name}")
            println(coroutineContext[CoroutineDispatcher])
            println(coroutineContext[CoroutineName])
            delay(5000L)
        }

        launch(Dispatchers.Default + CoroutineName("launch1")) {
            println("launch2: ${Thread.currentThread().name}")
            println(coroutineContext[CoroutineDispatcher])
            println(coroutineContext[CoroutineName])
            delay(10L)
        }
    }
}

* 부모의 컨텍스트를 연결해서 만들어진다. 

* 앨리먼트끼리 + 로 연결해준 것을 컨텍스트라고 하고,  넣고 빼고를 할 수 있다. 

코루틴 내에서 Context를 접근 할수 있다 

coroutineContext[CoroutineDispatcher] // 디스페처명 가져오기 
coroutineContext[CoroutineName] // 코루틴 이름 가지고 오기 

728x90
반응형