4장. 일시 중단 함수와 코루틴 컨텍스트

Minsuk Jang·2021년 11월 2일
0
post-thumbnail

일시 중단 함수

  • suspend 제어자만 추가하면 된다.
  • delay()와 같은 일시 중단 함수를 직접 호출 가능
  • 다른 일시 중단 연산에서만 호출될 수 있다
fun main(args : Array<String>){
	runBlocking{
    	greetDelayed(1000)
    }
}

suspend fun greetDelayed(delayMillis : Long){
	delay(delayMillis)
    println("Hello, world!")
}

비 일시 중단 코드에서 함수를 호출하기 위해선 코루틴 빌더로 감싸야한다.

디스패쳐 (Dispatcher)

  • 코루틴이 실행될 스레드를 결정해준다.
    • 시작될 곳과 중단 후 재개될 곳을 모두 포함

Unconfined

  • 첫 번째 중단 지점에 도달할 때까지 현재 스레드에서 실행
  • 일시 중단 후에는 일시 중단 연산에서 사용되는 기존 스레드에서 다시 시작
fun main(args : Array<String>) : Unit = runBlocking{
    GlobalScope.launch(Dispatchers.Unconfined) {
        println("Starting in ${Thread.currentThread().name}")
        delay(1000L)
        println("Resuming in ${Thread.currentThread().name}")
    }.join()
}

중단 전까지는 main에서 실행되다 중단 후, DefaultExecutor에서 실행되는 것을 확인할 수 있다.

단일 스레드 컨택스트

  • 항상 코루틴이 특정 스레드 안에서 실행되는 것을 보장
  • newSingleThreadContext()를 이용하여 디스패처를 생성
fun main(args : Array<String>) : Unit = runBlocking{
    val dispatcher = newSingleThreadContext(name = "myThread")

    GlobalScope.launch(dispatcher){
        println("Starting in ${Thread.currentThread().name}")
        delay(500L)
        println("Resuming in ${Thread.currentThread().name}")
    }.join()
}

하나의 스레드 (myThread)에서 계속 실행되는 것을 확인할 수 있다.

스레드 풀

  • 해당 풀에서 가용한 스레드에서 코루틴이 시작, 재개한다.
fun main(args : Array<String>) : Unit = runBlocking{
    val dispatcherPool = newFixedThreadPoolContext(4,"myPool")

    GlobalScope.launch(dispatcherPool) {
        println("Staring in ${Thread.currentThread().name}")
        delay(500L)
        println("Resuming in ${Thread.currentThread().name}")
    }.join()
}

예외 처리

  • 디스패쳐뿐만 아니라 handler를 넣어서 예외 처리가 가능하다.
fun main(args : Array<String>) : Unit = runBlocking{
    val handler = CoroutineExceptionHandler { context, throwable ->
        println("Error captured in $context")
        println("Message: ${throwable.message}")
    }

    GlobalScope.launch(handler) {
        TODO("Not implemented yet!")
    }

    delay(500L)
}

Non-cancellable

  • 취소 중인 코루틴은 일시 중단될 수 없게 설계돼 있다.
  • 코루틴이 취소된 동안 일시 중단을 하기 위해선 NonCancellable 컨텍스트를 사용해야한다.
fun main(args : Array<String>) : Unit = runBlocking{
    val duration = measureTimeMillis {
        val job = launch {
            try{
                while(isActive){
                    delay(500L)
                    println("still running")
                }
            }finally {
                withContext(NonCancellable) {
                    println("cancelled, will end now")
                    delay(5000L)
                    println("delay completed, bye bye")
                }

            }
        }
        delay(1200L)
        job.cancelAndJoin()
    }

    println("Took $duration ms")
}

컨텍스트에 대한 추가 정보

컨텍스트 조합

  • 더하기 연산자를 통해 결합이 가능하다.
fun main(args : Array<String>) : Unit = runBlocking{
    val dispatcher = newSingleThreadContext("myThread")
    val handler = CoroutineExceptionHandler { context, throwable ->
        println("Error captured")
        println("Message: ${throwable.message}")
    }

    GlobalScope.launch(dispatcher + handler) {
        println("Running in ${Thread.currentThread().name}")
        TODO("Not implemented")
    }.join()
}

컨텍스트 분리

  • minusKey()를 이용하여 컨텍스트 요소를 제거할 수 있다.
fun main(args : Array<String>) : Unit = runBlocking{
    val dispatcher = newSingleThreadContext("myThread")
    val handler = CoroutineExceptionHandler { context, throwable ->
        println("Error captured")
        println("Message: ${throwable.message}")
    }

    val context = dispatcher + handler
    val tmpCtx = context.minusKey(dispatcher.key)

    GlobalScope.launch(tmpCtx) {
        println("Running in ${Thread.currentThread().name}")
        TODO("Not implemented")
    }.join()
}

myThread가 아닌 Default 스레드가 실행되는 것을 확인할 수 있다.

withContext

  • 코드 블록 실행을 위해 주어진 컨텍스트를 사용할 일시 중단 함수이다.
  • withContext() 함수는 Job or Deferred를 반환하지 않고 전달한 람다의 마지막 구문에 해당하는 값을 반환한다.
fun main(args : Array<String>) : Unit = runBlocking{
    val dispatcher = newSingleThreadContext("myThread")
    val name = GlobalScope.async(dispatcher) {
        "Susan Calvin"
    }.await()

    println("User $name")

    val name1= withContext(dispatcher){
        "Susan Calvin"
    }
    println("User1 $name1")
}

동일한 Susan Calvin의 결과 값을 확인할 수 있다.

참고

  • learning Concurrency in Kotlin
profile
Positive Thinking

0개의 댓글