[Kotlin] Ch6-3. 코루틴 문맥과 스코프 기본

leeeha·2022년 9월 9일
0

코틀린

목록 보기
29/29
post-thumbnail

코루틴 문맥 (Coroutine Context)

  • 코루틴을 실행하기 위한 다양한 설정값을 가진 관리 정보 (코루틴 이름, 디스패처, 작업 세부사항, 예외 핸들러 등)
  • 디스패처는 코루틴 문맥을 보고 어떤 스레드에서 실행되고 있는지 식별할 수 있다.
  • 코루틴 문맥은 + 연산으로 조합될 수 있다.
val someCoroutineContext = someJob + Dispatchers.IO + someCoroutineName + someExceptionHandler 

CoroutineName

코루틴에 이름을 주며, 디버깅을 위해 사용됨.

val someCoroutineName = CoroutineName("someCoroutineName")

Job

작업 객체를 지정할 수 있으며, 취소 가능 여부에 따라 SupervisorJob()을 사용함.

val parentJob = SupervisorJob() // 자식에서 취소되더라도 부모와 다른 자식에 영향 X 
val someJob = Job(parentJob) 

CoroutineDispatcher

Dispatchers.Default, ...IO 등을 지정할 수 있으며, 필요에 따라 스레드풀 생성 가능

val myPool = Executors.newFixedThreadPool(2).asCoroutineDispatcher() 

CoroutineExceptionHandler

  • 코루틴 문맥을 위한 예외처리를 담당하며 코루틴에서 예외가 던져지면 처리한다.
  • 예외가 발생한 코루틴은 상위 코루틴에 전달되어 처리될 수 있다. (스코프를 가지는 경우, 예외 처리 에러를 잡아서 처리할 수 있음.)
  • 만일 예외 처리가 자식에만 있고 부모에 없는 경우, 부모에도 예외가 전달되므로 주의해야 한다. (이 경우 앱이 crash 되는 위험이 있음.)
  • 예외가 다중으로 발생하면 최초의 하나만 처리되고 나머지는 무시됨.
val someExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> 
	val coroutineName = coroutineContext[CoroutineName]?.name ?: "default coroutine name"
    println("Error in $coroutineName: ${throwable.localizedMessage}") 

}

코루틴의 스코프

GlobalScope

  • 독립형 (Standalone) 코루틴을 구성
  • 생명주기는 프로그램 전체 (top-level)에 해당하는 범위를 가지며, main의 생명 주기가 끝나면 같이 종료됨.
  • Dispatchers.Unconfined와 함께 작업을 실행하면, 서로 완전히 무관한 전역 범위에서 실행됨. (디스패처에 대해서는 뒤에서 자세히 살펴볼 예정)
  • 보통 GlobalScope 상에서는 launch나 async 사용이 권장되지 않음.
val scope1 = GlobalScope 
scope1.launch { ... }
scope1.async { ... }

or

GlobalScope.launch { ... }
val job1 = GlobalScope.launch { ... } // Job 객체 -> Job.join()으로 대기 
val job2 = GlobalScope.async { ... } // Deferred 객체 -> Deferred.await()으로 대기 (실행 결과 반환) 

CoroutineScope

  • 특정 목적의 디스패처를 지정한 범위를 블록으로 구성할 수 있다.
  • 모든 코루틴 빌더는 CoroutineScope의 인스턴스를 갖는다.
  • launch{...}와 같이 인자가 없을 때는 CoroutineScope에서 상위 문맥이 상속되어 결정됨.
  • launch(Dispatchers.옵션인자){...}와 같이 디스패처의 스케줄러를 지정할 수 있음. (Default 인자는 GlobalScope에서 실행되는 문맥과 동일함.)
val scope2 = CoroutineScope(Dispatchers.Default)
val routine1 = scope2.launch { ... }
val routine2 = scope2.async { ... }

or

launch(Dispatchers.Default) { ... }
async(Dispatchers.Default) { ... } 

예제

package chap06.section2

import kotlinx.coroutines.*

// 메인 함수 자체가 코루틴 스코프 
fun main() = runBlocking {
    launch {
        delay(1200L)
        println("Task from runBlocking") // 4
    }

    coroutineScope {
        launch {
            delay(200L)
            println("Task from nested launch") // 2
        }
        delay(200L)
        println("Task from coroutineScope") // 1
    }

    println("end of runBlocking") // 3
}

Task from coroutineScope
Task from nested launch
end of runBlocking
Task from runBlocking

package chap06.section2

import kotlinx.coroutines.*

// 메인 함수 자체가 코루틴 스코프가 되도록
fun main() = runBlocking {
    launch {
        delay(1200L)
        println("Task from runBlocking") // 2
    }

    coroutineScope {
        launch {
            delay(1500L)
            println("Task from nested launch") // 3
        }
        delay(200L)
        println("Task from coroutineScope") // 1 
    }

    println("end of runBlocking") // 4 
}

Task from coroutineScope
Task from runBlocking
Task from nested launch
end of runBlocking

package chap06.section2

import kotlinx.coroutines.*

fun main() = runBlocking(Dispatchers.Default) {
    launch(Dispatchers.IO) {
        delay(1200L)
        println("Task from runBlocking") // 2
    }

    coroutineScope {
        launch {
            delay(1500L)
            println("Task from nested launch") // 3
        }
        delay(200L)
        println("Task from coroutineScope") // 1
    }

    println("end of runBlocking") // 4
}

위의 코드처럼 runBlocking, launch의 Context 인자에 특정 디스패처를 지정해줄 수도 있다.


Thread Pool

  • 보통 CommonPool이 지정되어 코루틴이 사용할 스레드의 공동 풀을 사용한다.
  • 이미 초기화 되어 있는 스레드 중에 하나 혹은 그 이상을 선택하여 초기화 하기 때문에 스레드 생성에 걸리는 오버헤드가 없어서 빠르다.
  • 하나의 스레드에 다수의 코루틴이 동작할 수 있다.
val threadPool = Executors.newFixedThreadPool(4) // 스레드 개수 직접 지정하는 경우 
val myContext = threadPool.asCoroutineDispatcher()
...
async(myContext) { ... } 
profile
습관이 될 때까지 📝

0개의 댓글